fix(review): degrade gracefully when CI check status is unavailable (MNG-1750)#1494
Merged
Merged
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
nhopeatall
approved these changes
Jul 15, 2026
nhopeatall
left a comment
Collaborator
There was a problem hiding this comment.
Summary
APPROVE — a correct, tightly-scoped fix that makes CI check-status fetch non-fatal during review-agent boot. I traced the root cause and verified the fix end to end.
Verification performed
- Root cause confirmed. Phase 2 of the context pipeline (
profiles.ts:247-253) does not wrap trigger-pipeline steps in try/catch, so a 403 thrown bygetCheckSuiteStatusinsidefetchPRContextSteppropagates and is re-wrapped asBootFailureError. TheprContext→fetchPRContextStepmapping (strategies.ts:36) andgetCheckSuiteStatus's use of the Actions API (github/client.ts:358, which a fine-grained PAT without Actions: Read rejects) both check out. - Scope is correct. Only
getCheckSuiteStatus+formatCheckStatusare inside the try/catch;getPRandgetPRDiffremain fatal. - Secret hygiene. Only
error.messageis logged/injected (error instanceof Error ? error.message : String(error)), never the raw OctokitRequestError;formatCheckStatusUnavailabletakes a plain string. Consistent with the rest ofcontextSteps.ts. - Success path unchanged,
checkStatusFormatted/checkStatusDescriptionare definitely-assigned in both branches. - "Retry" guidance is accurate — the review agent has
scm:read, which provides theGetPRChecksgadget for mid-run re-fetch. - Strict improvement across all 7
prContextconsumers (review, respond-to-ci/review/pr-comment, resolve-conflicts): agetCheckSuiteStatuserror previously crashed boot for all of them; now it degrades. No regression —respond-to-cigets its real signal fromGetCIRunLogs, not the boot summary. - Tests — ran the two touched files locally: 54 passed. Docs guard is satisfied (
AGENTS.mdis a symlink toCLAUDE.md, so both stay byte-identical).
Minor (non-blocking, optional)
formatCheckStatusUnavailablepresents the "Probable cause: ... 403 ... Actions: Read" explanation for every caught error, but the catch is broad (any failure — transient network blip, 429, 500 — not just 403). The message is hedged as "Probable cause" and always includes the realUpstream error: <message>, and the agent can re-fetch viaGetPRChecks, so this is cosmetic — the 403/PAT case is the overwhelmingly common one. No change required.
🕵️ claude-code · claude-opus-4-8 · run details
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes MNG-1750 — the
reviewagent boot no longer dies withBootFailureError: plan resolution failedwhen the reviewer credential is a fine-grained PAT lacking the Actions: Read permission.Previously,
fetchPRContextStepcalledgithubClient.getCheckSuiteStatus(...)unconditionally. A reviewer PAT without Actions: Read makes the GitHub Actions API return a 403 (Resource not accessible by personal access token), which propagated up through the Phase-2 context pipeline (profiles.tsdoes not wrap steps in try/catch) and got re-wrapped as aBootFailureError. Because boot failure precedes log persistence, this surfaced as a "silent 22s crash" with a NULLcascade_log.What changed
1. New
formatCheckStatusUnavailableformatter —src/gadgets/github/core/getPRChecks.tsAn exported helper next to the existing
formatCheckStatus. Produces a message that is visibly distinct fromformatCheckStatus'sNo CI checks configuredstring, so the agent can tell "CASCADE tried to read CI status and failed" apart from "there is nothing to check". Includes the Actions: Read permission hint and takes a plain string (never a raw OctokitRequestError, which can carry theAuthorizationheader).2. Non-fatal check-status fetch —
src/agents/definitions/contextSteps.tsWrapped only the
getCheckSuiteStatus+formatCheckStatuscalls in a tight try/catch. On failure:WARN CI check status unavailablewith the sanitizederror.message(never the raw error object).formatCheckStatusUnavailabletext as theGetPRChecksresult and flips the description toCI check status unavailable.getPRandgetPRDiffremain outside the try/catch — they stay fatal, because a review without the PR itself is meaningless. TheGetPRChecksinjection is still pushed unconditionally; on the success path theresult,params, anddescriptionare byte-identical to before.3. Docs —
CLAUDE.md(andAGENTS.md, a symlink to it)Added one paragraph to the "Review agent — context shape (debugging)" section documenting the informational-not-fatal CI-status behavior.
AGENTS.mdis a symlink toCLAUDE.md, so both stay byte-identical (guarded bytests/unit/architecture-docs.test.ts).Tests
tests/unit/agents/definitions/contextSteps.test.ts— newMNG-1750 — graceful CI check-status degradationblock:getCheckSuiteStatus→ boot proceeds +UNAVAILABLEinjection carrying the upstream message +Actions: Readhint +description === 'CI check status unavailable'.WARNlog carries the upstream error message.GetPRChecksresult containsPR #1092 Check Status: 1/1, description unchanged (Pre-fetched CI check status).getPRfailure still throws (PR not found).getPRDifffailure still throws (diff unavailable) — locks the "diff is fatal" invariant.tests/unit/gadgets/github/core/getPRChecks.test.ts— focusedformatCheckStatusUnavailabletests: contains the error message, theActions: Readhint, the PR number, is distinct fromNo CI checks configured(via theUNAVAILABLEmarker), and never emits[object Object].Verification
npx vitest run tests/unit/agents/definitions/contextSteps.test.ts tests/unit/gadgets/github/core/getPRChecks.test.ts→ 54 passednpx vitest run tests/unit/agents/ tests/unit/gadgets/github/→ 1038 passed (no adjacent regressions)npx vitest run tests/unit/architecture-docs.test.ts→ 41 passed (docs sync guardrail)npm run typecheck→ cleannpx biome checkon changed files → cleanOut of scope (unchanged)
Router-side
getCheckSuiteStatuscallers gate dispatch decisions and deliberately fail closed (check-suite-success,check-suite-failure,pr-ready-to-merge,post-completion-review). The mid-rungetPRChecksgadget already returns errors through its structured envelope. None of those were touched.🤖 Generated with Claude Code
🕵️ claude-code · claude-opus-4-8 · run details