You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
report-lane-outcome: record a key-to-subscription fallback as a lane-record token #806
When a caller runs run-claude-cli as two jobs (key job first, subscription-token job only after the key job's credit stop, the v0.54.0 pattern), the only lane record for a run comes from the subscription job. report-lane-outcome has no input saying that the role's API key hit a credit stop, so that fact is not a key=value token in the lane record.
The owner rotates a spare account's API key into a role by hand when that role's key runs out. The record of that event is what they act on, and today it is not machine-readable:
claude-code-plugins #8577 works around it by writing the note into the human-readable lane input (pr-verify (key AUTOMATIONS_CLAUDE_API_KEY hit a credit stop)) plus a ::warning::.
harness-ops morning brief reads CI lane records as tokens from job annotations (class=, turns=, max-turns=; plugins/harness-ops/skills/morning-brief/scripts/morning-brief.sh:1410-1425 in claude-code-plugins). The lane text is prose, so the credit stop never shows in the brief's lane rows.
Proposal
Add an optional report-lane-outcome input, for example fallback-reason with a fixed vocabulary (credit-stop, no-key), printed as a fallback=<reason> token in the lane record next to credential=. Callers in the two-job pattern pass it from the key job's lane-class output.
Then, in claude-code-plugins:
pr-verify.yml sets lane: pr-verify again and passes the new input.
The morning brief counts fallback=credit-stop per lane and credential role, so the rotation signal shows in the brief.
Acceptance
A run whose key job credit-stops writes one lane record containing credential=<ROLE>_CLAUDE_CODE_OAUTH_TOKEN and fallback=credit-stop.
Decision: Add an optional fallback-reason input to report-lane-outcome with exactly two values, credit-stop and no-key, printed as a fallback=<reason> token in the lane record. Wire it into ci-workflows' own intake-triage subscription job in the same PR.
report-lane-outcome inputs (action.yml:30-140) have no fallback input. recordTokens (classify.cjs:310-330) emits resets=, resets-window=, credential= (:320), turns=, max-turns= and models=.
In intake-triage the subscription job runs only when needs.claude.outputs.lane-class == 'credit-stop' or needs.claude.outputs.credential-present == 'false' (intake-triage.yml:832-835). The key job skips its report step on a credit stop (:626-628), so the subscription job's "Report triage outcome" step (:1174-1186) writes the run's only lane record, with credential: but no sign of why it ran.
report-lane-outcome/action.yml: new optional input fallback-reason, default empty, documented with its two values.
classify.cjsrecordTokens: when the input is set, push fallback=<reason> immediately after credential=.
Reject any other non-empty value: the step fails with a message naming the allowed values.
intake-triage subscription job report step: pass fallback-reason: credit-stop when needs.claude.outputs.lane-class == 'credit-stop', else no-key when needs.claude.outputs.credential-present == 'false'.
README or action description entry for the new input.
Out of scope: an auth value for a dead key; claude-code-plugins changes (pr-verify passing the input, the harness-ops morning brief counting the token) follow in that repo after a repin.
Acceptance criteria:
fallback-reason: credit-stop with credential: X_CLAUDE_CODE_OAUTH_TOKEN yields one lane record containing both credential=X_CLAUDE_CODE_OAUTH_TOKEN and fallback=credit-stop, with fallback= right after credential= (test in classify.test.cjs or outcome-step.test.cjs).
An empty fallback-reason yields no fallback= token, and existing records are byte-identical (existing tests unchanged and passing).
A value outside credit-stop / no-key fails the step (test).
The intake-triage subscription job passes fallback-reason derived from needs.claude.outputs.lane-class and needs.claude.outputs.credential-present; the leg parity test .github/scripts/intake-triage-legs.test.cjs is updated to allow this one difference, and node --test .github/scripts/*.test.cjs passes.
Sequencing / dependencies: independent; touches the same subscription job as #804, so rebase whichever lands second. Downstream consumer: claude-code-plugins #8577 workaround and the harness-ops morning brief.
Evidence: the vocabulary matches the two triggers of the subscription job's if at intake-triage.yml:835. Placement next to credential= follows classify.cjs:320. No external research was needed.
Activity
kyle-sexton commentedon Oct 20, 2026
Agent-ready brief (triaged 2026-10-20)
Decision: Add an optional
fallback-reasoninput toreport-lane-outcomewith exactly two values,credit-stopandno-key, printed as afallback=<reason>token in the lane record. Wire it into ci-workflows' own intake-triage subscription job in the same PR.Current state (origin/main
834f4e1):report-lane-outcomeinputs (action.yml:30-140) have no fallback input.recordTokens(classify.cjs:310-330) emitsresets=,resets-window=,credential=(:320),turns=,max-turns=andmodels=.subscriptionjob runs only whenneeds.claude.outputs.lane-class == 'credit-stop'orneeds.claude.outputs.credential-present == 'false'(intake-triage.yml:832-835). The key job skips its report step on a credit stop (:626-628), so the subscription job's "Report triage outcome" step (:1174-1186) writes the run's only lane record, withcredential:but no sign of why it ran.Scope:
report-lane-outcome/action.yml: new optional inputfallback-reason, default empty, documented with its two values.classify.cjsrecordTokens: when the input is set, pushfallback=<reason>immediately aftercredential=.subscriptionjob report step: passfallback-reason: credit-stopwhenneeds.claude.outputs.lane-class == 'credit-stop', elseno-keywhenneeds.claude.outputs.credential-present == 'false'.Out of scope: an
authvalue for a dead key; claude-code-plugins changes (pr-verify passing the input, the harness-ops morning brief counting the token) follow in that repo after a repin.Acceptance criteria:
fallback-reason: credit-stopwithcredential: X_CLAUDE_CODE_OAUTH_TOKENyields one lane record containing bothcredential=X_CLAUDE_CODE_OAUTH_TOKENandfallback=credit-stop, withfallback=right aftercredential=(test inclassify.test.cjsoroutcome-step.test.cjs).fallback-reason: no-keyyieldsfallback=no-key(test).fallback-reasonyields nofallback=token, and existing records are byte-identical (existing tests unchanged and passing).credit-stop/no-keyfails the step (test).fallback-reasonderived fromneeds.claude.outputs.lane-classandneeds.claude.outputs.credential-present; the leg parity test.github/scripts/intake-triage-legs.test.cjsis updated to allow this one difference, andnode --test .github/scripts/*.test.cjspasses.Sequencing / dependencies: independent; touches the same subscription job as #804, so rebase whichever lands second. Downstream consumer: claude-code-plugins #8577 workaround and the harness-ops morning brief.
Evidence: the vocabulary matches the two triggers of the subscription job's
ifat intake-triage.yml:835. Placement next tocredential=follows classify.cjs:320. No external research was needed.fix(intake-triage): run the model read-only and apply in a model-free…
feat(report-lane-outcome): record why a fallback credential ran (#834)