Skip to content

feat(ci-status): add an opt-in mode in which no run waits for another - #652

Merged
kyle-sexton merged 1 commit into
mainfrom
feat/ci-status-no-wait
Oct 2, 2026
Merged

kyle-sexton merged 1 commit into
mainfrom
feat/ci-status-no-wait

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

No related issue: ci-workflows has no issue for this; it is the upstream half of the no-wait ci-status design for claude-code-plugins (see Related).

Summary

A contract-only run (edited, labeled, unlabeled) could only wait for the full run's ci-lanes verdict. It held a runner for up to the full run's wall and went red at the ceiling whenever the full run was slower. This adds an opt-in mode in which no run waits for another. Every existing input keeps its default, so current consumers are unaffected.

New inputs on .github/actions/ci-status (both default 'false'):

  • record-pending: called from the full run's first job, it marks ci-lanes pending on the head SHA and stops. A contract-only run that reads the status while the lanes run then fails instead of carrying an older success forward (a draft run's, for example). The full run's gate overwrites the marker with its verdict. It writes only on a same-repository pull request event that is not contract-only; otherwise it passes with a notice. Needs statuses: write.
  • rerun-contract-only-siblings: after a full run records success, it calls POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun-failed-jobs on every failed run of the same workflow on the SHA whose latest attempt has one failed job and every other job skipped. Full runs and the run itself are never re-run, and a re-run attempt is contract-only again, so it cannot loop. Needs actions: write; a refusal only warns and never changes the recorded verdict.

Recommended consumer setting: carry-forward-wait-seconds: '0', timeout-minutes: 3, both inputs 'true'.

Fix

  • carry-forward-wait-seconds: '0' already read the status once and made no Actions call (run.sh gates the wait on > 0). Unchanged; now pinned by tests that count exactly one status read and no sleep, and documented as the recommendation.
  • The wait ceiling counts elapsed wall-clock time from the start of the wait (date +%s), API calls included, instead of summed sleeps. A large ceiling no longer overruns the job budget sized for it.
  • Each carry-forward red names the remedy for the state it read instead of always saying "re-run the full workflow": pending names the full run in flight (its ci-status supersedes the red), failure/error names the run that recorded it, absent gives both cases. With rerun-contract-only-siblings on, the closing sentence says the full run re-runs this run, and to re-run it by hand only if it stays red.
  • The status write (full mode and pending mode) moves into one write_status helper with the same retries; its refusal message now says "the calling job needs statuses: write".
  • README: the no-wait configuration with an example, permissions, and the residual gap (a contract-only run still in flight when the full run lists its siblings is not re-run); the existing sizing rule stays for consumers that keep a wait.

Verification

Local (Windows, Git Bash):

  • bash .github/actions/ci-status/run.test.sh: 81 cases, all pass (61 before this change). New cases cover: wait 0 reads the status exactly once with no sleep and no Actions call (red and green); the ceiling counts wall-clock time (a listing that takes 10 s reaches a 30 s ceiling after one sleep); pending newer than success fails; the success path re-runs only the failed contract-only sibling, never a failed full run, a single-job run, a green/in-flight/cancelled run, or itself, and only after the status write; failure, default-off and fork runs re-run nothing; a refused re-run, a failed jobs read, a 403 listing and a malformed listing all warn and keep the run green; a contract-only run with the input on never re-runs anything; pending mode writes on pull_request/pull_request_target, skips on contract-only, fork and push, fails on a refused write; invalid values for both inputs are rejected; both inputs default to 'false' in action.yml.
  • node --test .github/scripts/*.test.cjs: 159 pass, 0 fail.
  • shellcheck --rcfile .shellcheckrc and shfmt -d on the composite: clean. actionlint .github/workflows/ci.yml: clean. markdownlint-cli2@0.23.2 README.md: 0 issues.

Not exercised here: the live rerun-failed-jobs call with a GITHUB_TOKEN holding actions: write. GitHub's permissions table lists the endpoint under Actions write for installation tokens; the first consumer probe settles it.

Related

🤖 Generated with Claude Code

A contract-only run could only wait for the full run's ci-lanes verdict,
holding a runner for up to the full run's wall and going red at the ceiling
whenever the full run was slower. Two opt-in inputs remove the wait:

- record-pending, called from the full run's first job, marks ci-lanes
  pending on the head SHA and stops. A contract-only run that reads the
  status while the lanes run then fails instead of carrying an older success
  forward. It writes only on a same-repository pull request event that is
  not contract-only, and passes with a notice otherwise.
- rerun-contract-only-siblings makes a full run that records success call
  rerun-failed-jobs on every failed run of the same workflow on the SHA
  whose latest attempt has one failed job and every other job skipped. Full
  runs and the run itself are never re-run, and a re-run is contract-only
  again, so it cannot loop. A refusal only warns.

carry-forward-wait-seconds '0' already read the status once with no Actions
call; tests now pin that, and the README recommends it with a 3-minute job
budget.

The wait ceiling now counts elapsed wall-clock time instead of summed
sleeps, and each carry-forward red names the remedy for the state it read
(pending, failure or error, absent) instead of always saying to re-run the
full workflow. Every existing input keeps its default; both new inputs
default to false.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 2, 2026 23:26
@kyle-sexton
kyle-sexton merged commit 0e37a01 into main Oct 2, 2026
29 checks passed
@kyle-sexton
kyle-sexton deleted the feat/ci-status-no-wait branch October 2, 2026 23:26
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-02T23:31:14.856328Z 89b1373 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@claude

claude Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 22s —— View job


Security review — skipped

  • Gather context
  • Check skip gate

This PR is merged (state: MERGED), so per the skip gate in /review:security-review ("PR is closed or not open"), no deep review is performed and nothing further is posted.

If a security review is wanted on this change, it would need to run as a seat-run pass referencing the merged commit, or be re-triggered on a new PR/branch.

@claude

claude Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 14s —— View job


/review:code-review — skip gate triggered

Result: no review performed. PR #652 is already merged (state: MERGED). The /review:code-review skip gate stops before deep review whenever the PR is closed or not open, and posts nothing further in that case — so no inline comments were made.

If you'd like a retrospective review of the merged changes anyway (e.g. to catch follow-up issues before a future PR), let me know and I can run it as an informational pass outside the normal lane semantics.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89b13739d2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

<"$gh_stdout")"
for id in $candidates; do
# shellcheck disable=SC2310 # gh_api handles its own errexit; the caller classifies the status.
if ! gh_api GET "repos/${REPOSITORY}/actions/runs/${id}/jobs?filter=latest&per_page=100"; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Paginate the jobs before classifying contract-only runs

When a valid workflow expands past 100 jobs, this request retrieves only the first page: GitHub documents per_page as capped at 100, while a matrix may create up to 256 jobs (jobs endpoint, matrix limit). The following shape test therefore may not see the failed gate on a later page, so rerun-contract-only-siblings silently leaves that contract-only run red; fetch and combine every jobs page before classifying it.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant