Skip to content

feat(pr-pipeline): run pr-verify's model on the key first and the subscription token only on a credit stop - #8577

Merged
kyle-sexton merged 5 commits into
mainfrom
feat/pr-verify-two-job
Oct 20, 2026
Merged

kyle-sexton merged 5 commits into
mainfrom
feat/pr-verify-two-job

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

No related issue: run-claude-cli v0.54.0 (ci-workflows 7b36242) takes exactly one credential, so pr-verify needs the two-job pattern before its pin can move.

Summary

ci-workflows v0.54.0 makes the run-claude-cli action take exactly one of api-key or subscription-token, fail closed if given both, and set lane-class=credit-stop on a key credit stop instead of falling back inside the step. pr-verify pins v0.53.0, which fell back in-step. This moves pr-verify to two jobs and repins to v0.54.0 (7b362428).

Fix

  • check-intent runs the model on the automations API key. New outputs: lane-class, credential-present, key-leg-clean.
  • check-intent-subscription (needs: check-intent, ubuntu-24.04, fresh checkout, every step repeated, nothing carried from the first job) runs only when the key job hit a credit stop with a clean key leg, or no key is set (if: at :364; with no status function GitHub adds success(), so a failed key job skips it).
  • secrets.AUTOMATIONS_CLAUDE_CODE_OAUTH_TOKEN is now only in the subscription job (:490, :563). Permissions unchanged, read-only in both jobs.
  • Lane-funding conditions kept: key first (automations credits spent first); each job echoes credential: in report-lane-outcome; a new "Check the key leg" step (:300) fails the key job on any permission denial, and write-verdict.mjs still catches denials in the subscription job, so a denial never yields a verdict; exactly one verdict per run (a credit stop always fails the key job's judge step). Step names "Judge the pull request" and "Upload the verdict" kept.
  • Job count: a skipped job uses no runner, so the steady state adds one skipped job per run; on a credit stop the jobs run one after the other.

Known interaction: the held reconciler change (#8562) counts a started judge step in any job, so it would hold rather than re-run a run whose subscription job was lost. That fails safe; the fix belongs in #8562.

Verification

  • workflow.test.mjs rewritten per job: hand-off condition, each secret only in its own job, the subscription job's steps equal the key job's apart from the credential (a mutation check fails on drift). New tests for checkKeyLeg in write-verdict.mjs. resolve-pr, reconcile, callers and workflow-gates tests pass; actionlint, offline zizmor and runner-policy pass.
  • Needs GitHub Security & Cost's pass before merge (secrets, needs, if changes).

Related

🤖 Generated with Claude Code

https://claude.ai/code/session_01Xk7Lz7d2twxvA3LfRxnLkT

kyle-sexton and others added 3 commits October 20, 2026 04:58
…scription token only on a credit stop

run-claude-cli v0.54.0 takes exactly one credential and leaves the
subscription fallback to a second job. check-intent now holds only the
API key; check-intent-subscription holds only the OAuth token and runs on
a fresh ubuntu-24.04 VM only when check-intent's key leg ended in a credit
stop with no permission denial, or the key is empty. In those cases
check-intent writes no verdict and does not fail, so one job per run
judges. The subscription job repeats every step from a fresh checkout and
reads only three check-intent outputs, in its if.

write-verdict.mjs gains a check-key-leg mode that fails on any denial in
the key leg's log, since the subscription job cannot read that log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xk7Lz7d2twxvA3LfRxnLkT
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 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-20T09:02:21.046623Z 4391955 PR opened
ℹ️ 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.

Comment thread .github/workflows/pr-verify.yml
@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed d8df88729e6a47516b989631813c1bc870a74754: 0 finding(s), 0 as inline comments.

No findings. The only change since 5a2a4ab is a rewording of one header comment in .github/workflows/pr-verify.yml, lines 24-25. No executable lines changed.

I checked the new wording against the file:

  • Outputs read: The second job reads exactly three check-intent outputs: lane-class, key-leg-clean and credential-present. They appear in needs.check-intent.outputs at lines 379-381 and 605.
  • if comparisons: Each of the three is compared to a fixed string in the job's if (lines 379-381).
  • Lane record name: lane-class is used once more, at line 605, to choose between two literal lane names.
  • No run: script: None of the three outputs is written into a run: script.

The comment is accurate, and the rewording removes the earlier misleading semicolon clause. I did not run the workflow or CI. This review covers only the incremental diff. The earlier commits were reviewed at 5a2a4ab.

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed d8df88729e6a47516b989631813c1bc870a74754: 0 finding(s), 0 as inline comments.

No security findings. The only change since 5a2a4ab is a rewording of a header comment in .github/workflows/pr-verify.yml (about how job outputs are read). It adds no executable lines, no permissions: changes, no trigger changes and no instruction-surface deletions. The comment's claim about outputs stays consistent with its description, so I skipped the deeper review.

…workflows to one release

A credit stop with a denial on the key leg left no lane record: the key
job skipped its report and the subscription job never ran. The key-leg
check now runs before the report, with continue-on-error, and the report
runs on that check's failure with class rate-limit.

A clean credit stop was recorded only by the subscription job, under the
OAuth credential. Its lane name now says whether the key hit a credit stop
or is empty, picked between two literals, and the key-leg step warns that
the automations key needs rotating.

Every ci-workflows action moves to v0.54.0 with run-claude-cli, and a
test holds them to one SHA. The subscription job's if repeats the key
job's event, ref and fork conditions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xk7Lz7d2twxvA3LfRxnLkT
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 20, 2026
Merged via the queue into main with commit e9e9c32 Oct 20, 2026
25 checks passed
@kyle-sexton
kyle-sexton deleted the feat/pr-verify-two-job branch October 20, 2026 09:30
kyle-sexton added a commit to melodic-software/ci-workflows that referenced this pull request Oct 20, 2026
Closes #806

## Summary

When intake-triage (or any caller of the two-job pattern) falls back to
the subscription token, the run's only lane record comes from the
subscription job and does not say why it ran. This adds a
machine-readable `fallback=<reason>` token so a credit stop on a role's
API key shows in the lane record.

## Fix

- `report-lane-outcome`: new optional `fallback-reason` input,
`credit-stop` or `no-key`. `recordTokens` prints `fallback=<reason>`
right after `credential=`, only for a value in `FALLBACK_REASONS`. The
step script fails (`core.setFailed`, naming the allowed values, before
any annotation or output) on any other non-empty value. The value
reaches the script through env only.
- `intake-triage.yml`: the subscription job's "Report triage outcome"
step passes `fallback-reason: ${{ needs.claude.outputs.lane-class ==
'credit-stop' && 'credit-stop' ||
needs.claude.outputs.credential-present == 'false' && 'no-key' || ''
}}`. That is a `with:` input on a `uses:` step, so the subscription job
still writes no `needs.` value into a `run:` or `script:`.
- `intake-triage-legs.test.cjs`: the parity test strips exactly this one
input from the subscription job (after asserting its exact text), and a
new test evaluates it: credit-stop gives `credit-stop`, an empty key
gives `no-key`, anything else gives empty.
- README: the run-record token list and the two-job pattern describe the
input.

Out of scope, per the brief: an `auth` value; the claude-code-plugins
follow-ups (pr-verify passing the input, the morning brief counting the
token).

## Verification

- `node --test .github/scripts/*.test.cjs .github/actions/*/*.test.cjs`:
632 pass, 0 fail.
- New tests were run red before the change: 3 in report-lane-outcome
(record token order and vocabulary, notice and class line carry
`credential=... fallback=...`, out-of-vocabulary value fails the step
with no outputs), and 2 in the legs test (parity and expression
evaluation) before the workflow edit.
- Existing record tests are unchanged and pass, so records without the
input are byte-identical.
- actionlint, biome, typos and markdownlint-cli2 (README) are clean. No
shell was touched.

## Related

- #804 / #821 (read-only model jobs) and #782 / #830 (trusted-comment
filter): this is built on their structure, and nothing they added is
relaxed.
- Downstream: melodic-software/claude-code-plugins#8577 (the workaround
this replaces after a repin).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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