Repository navigation
feat(pr-review-cli): skip the key job while the key's credit stop is recent - #882
Conversation
…recent A key out of credits still started the key job on every run: runner isolation, checkout and prep, a credit stop, then the handoff. A new key-credit job reads this workflow's completed runs from the last key-credit-window-hours (default 6) with the job token (actions: read): one list call and at most three job-list calls. When the key job ended in the launcher's credit-stop class in that window, the key job is skipped and the subscription job reviews; the status job then reads the subscription job's scope, and the last summary falls back to that leg. The key job records a credit stop as a step named for its run, run only after lane-class credit-stop, so an auth stop never counts; a skipping run carries the origin forward the same way, so the window counts from the credit stop. Any read failure or unclear answer runs the key job as before. Callers grant actions: read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
…run the key Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 50s —— View job Security review complete
Scope: No security issues found. Notes from the review:
critical-findings: 0 claude-lane-summary: lane=claude-security-review sha=85f8f0c922750b5fd5f9206ecfb98b6417cc5717 run=40116561026 |
|
Claude finished @kyle-sexton's task in 6m 16s —— View job Review complete
Scope note: PR body contract: compliant — opens with What I checked closely:
No correctness or convention findings to report. Not independently verified: I could not execute claude-lane-summary: lane=claude-review sha=85f8f0c922750b5fd5f9206ecfb98b6417cc5717 run=40116561026 |
|
claude-security-review has reviewed this pull request through 1d0d86a; a later push is reviewed from there. |
|
claude-review has reviewed this pull request through 1d0d86a; a later push is reviewed from there. |
…ts token description The naming lint requires a verb-first action directory, so the action and its job become check-key-credit. An unquoted "actions: read" in the token input's description broke the action's YAML for actionlint and zizmor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 57s —— View job Review complete
Scope: incremental — only the diff since PR body contract: no changes since the last pass; still compliant ( What this commit does: a pure mechanical rename — action directory Finding (posted inline):
Not independently verified: I could not run claude-lane-summary: lane=claude-review sha=38e9250d70942e26a9b36b6e4980e0b4c5082c99 run=40118174982 |
|
Claude finished @kyle-sexton's task in 50s —— View job Security review complete
Scope: incremental review of Skip gate applied (item 2 — no security-relevant surface): the entire incremental diff is a mechanical rename of the gate action and job id (
This increment carries no exploitable change beyond what was already reviewed at critical-findings: 0 claude-lane-summary: lane=claude-security-review sha=38e9250d70942e26a9b36b6e4980e0b4c5082c99 run=40118174982 |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 32s —— View job Security review complete
Scope: incremental review of Skip gate applied (item 2 — no security-relevant surface): the entire incremental diff is a pure formatting change — reflowing one inline object literal in a test assertion onto multiple lines: - needs: { "check-key-credit": { result, outputs: { "skip-key": skipKey } } },
+ needs: {
+ "check-key-credit": { result, outputs: { "skip-key": skipKey } },
+ },( No security-relevant surface in this increment. No new security issues to report; nothing beyond what was already reviewed at critical-findings: 0 claude-lane-summary: lane=claude-security-review sha=8cacbe6b3267336f5e5f78e95f8976c46af5032c run=40118446767 |
|
Claude finished @kyle-sexton's task in 31s —— View job Review complete
Scope: incremental — only the diff since Skip gate applied (item 3 — no review surface): the entire increment is a 3-line formatting reflow of one test's PR body contract: no changes since the last pass; still compliant ( No findings to report for this increment. claude-lane-summary: lane=claude-review sha=8cacbe6b3267336f5e5f78e95f8976c46af5032c run=40118446767 |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 26s —— View job Review complete
Scope: incremental — only the diff since What this commit does: fixes the stale cross-reference this lane flagged on the previous pass (inline comment on PR body contract: no changes since the last pass; still compliant ( No findings to report for this increment. claude-lane-summary: lane=claude-review sha=1d0d86a53d15b8d21ea7ac7faea0973e7695b13b run=40118654361 |
…contract (#877) Refs: melodic-software/ci-workflows#882 ## Summary Registers the `pr-review-cli.yml` reusable-workflow contract at ci-workflows [v0.65.8](https://github.com/melodic-software/ci-workflows/releases/tag/v0.65.8) (`4f79cc9244498ae856504cf74aeeb05dcc9c6b43`), so callers pinned to that release pass runner-policy. ## Fix Copies the v0.65.7 entry (`@4c531cea137f5cbb38d4c132dbfa6e38f76fba7d`) forward in `components/runner-policy/policy.json` and adds the matching copy-forward test in `runner-policy.test.mjs`, following #874 and #876. v0.65.8 ([ci-workflows#882](melodic-software/ci-workflows#882)) adds a `check-key-credit` job that skips the key job while the key's recent credit stop is inside `key-credit-window-hours`. Callers pass no new input. They grant `actions: read` on the calling job, which the contract does not record. Inputs, secrets and runner are unchanged. ## Verification - `node components/runner-policy/runner-policy.mjs --root <worktree>`: Runner policy passed. - `node --test components/runner-policy/runner-policy.test.mjs`: 350 pass, 0 fail. ## Related - [melodic-software/ci-workflows#882](melodic-software/ci-workflows#882) (released as v0.65.8) - #876 (the v0.65.7 contract) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

No related issue: owner decision Q57 (2026-10-24)
Summary
A keyed review lane whose API key is out of credits still starts the key job on every run: apt installs bubblewrap, checks out, preps, runs the launcher, gets a credit stop, and only then hands off to the subscription job. That is a wasted hosted job and extra API calls and delay on every review. Owner decision Q57, design (a): read this workflow's own recent runs and skip the key job while a credit stop is inside a 6-hour window.
Fix
.github/actions/check-key-credit(gate.cjs), run by a newcheck-key-creditjob (claude-review-key-credit/security-review-key-credit) beforereview-key. It holds no secret and onlyactions: read, and uses the job token, not the App token. It makes one list call (GET .../actions/workflows/<caller file>/runs?status=completed&created=>=<now-window>&per_page=100). Then, newest first, it makes at most 3 job-list calls (GET .../runs/<id>/jobs?filter=latest) and stops at the first run that settles the question.Record the key's credit stop in run ${{ github.run_id }}. It runs only whensteps.claude.outputs.lane-class == 'credit-stop', the launcher's class fromclassify.cjsisCreditStop, which only the key leg can end in. Anauthclass (401, or 402 without the credit text) never counts. When the gate skips, its own job runs a step with the same name pattern that carries the origin run id. The next run therefore finds the original credit stop in its newest run, and the window counts from that credit stop, not from the last skip. The first run after the window runs the key again, which picks up a new cycle or a replaced key.review-keyis skipped andreview-subscriptionstarts (needs: [check-key-credit, review-key],!cancelled()). The existing handoff path is unchanged and still requiresreview-keyto succeed. The status job runs when either leg ran. It reads the scope outputs (scope-review, skip, since, scope, marker-id, merge-base, pr-files) from the subscription job when the key was skipped, and takes the last summary from the subscription leg when the key leg left none. The subscription'sfallback-reasoniscredit-stop, so the lane records keep their existing value set. Check names, marker text, the review-complete notice and the lane-record format are unchanged.review-key'sifstarts with!cancelled()and skips only on an exactskip-key == 'true'. A failed gate job, a refused read, an unreadable ref, a window of 0 or no settling run within the cap all run the key as before.key-credit-window-hours(number, default 6). The gate writes one log line per run. A skip is a::notice title=check-key-credit::naming the origin run and its URL.actions: readon the job that uses this workflow. Without it the run fails at startup, because a called workflow can only lower the caller's grant. The header's canonical caller and the README say so.Verification
nice node --test .github/scripts/*.test.cjs: 701 pass, 0 fail.check-key-credit.test.cjs(9 tests) covers these cases:stepsfor every job (seen on claude-code-plugins run 40115785836) counts as no evidence, so the key runs after the cap.mainspends one list call and one jobs call on a skip, writes the notice and outputs, and records api-budget readings.pr-review-cli.test.cjs: 6 new or changed tests fail against main's workflow and pass now:check-key-credit.true, including when the gate fails.credit-stop.actionlintandbiome checkpass on the changed files.Unverified until live
The carry step's name renders
steps.gate.outputs.origin-run. The contexts table listsstepsforjobs.<job_id>.steps.name, and matrix values render in step names in the jobs API, but a rendered step output has not been observed. The first credit stop on main after the re-pin is the probe: check that the next run'scheck-key-creditjob skips with the notice, and that the skipping run's carry step shows a run id in its name. If it does not render, the gate still finds the key job's own record (its name usesgithub.run_id) for runs until that record falls out of the newest three, then runs the key again (fail open, smaller saving).Related
Other keyed lanes adopt it with the same three pieces:
key-jobset to the key job's name;Record the key's credit stop in run ${{ github.run_id }}, gated on the launcher'slane-class == 'credit-stop';review-key.result == 'skipped' && skip-key == 'true'.Wiring claude-code-plugins pr-unblock, pr-verify and the issue lane is out of scope here.
pr-pipeline-sandbox's callers need
actions: readwhen they bump past this release.🤖 Generated with Claude Code