Discovered in the agent-layer readiness sweep. The #730 fix (PR #732) did not cover the finalize path.
Problem
isCheckRunPermissionError (src/github/app.ts:293) treats any HTTP 403 as permission_missing. GitHub secondary/abuse rate limits return 403. When the finalizing createOrUpdateGateCheckRun (src/queue/processors.ts:1211) hits a transient 403, it returns permission_missing → only an audit row is written, the pending check is not finalized and no throw → the error-finalize catch never runs. The 'Gittensory Gate is evaluating' check is left in_progress forever, blocking merge.
Fix
Distinguish permanent (403 'resource not accessible by integration') from transient 403 (secondary rate limit; carries Retry-After / x-ratelimit-remaining:0). On transient → retry or fall through to neutral error-finalize so the pending check always terminalizes.
Why it gates the agent layer
A gate that takes actions must never hang. P1.
Acceptance
- Transient 403 at finalize → check ends neutral (not stuck); permanent 403 → permission audit as today. Test covers both.
Discovered in the agent-layer readiness sweep. The #730 fix (PR #732) did not cover the finalize path.
Problem
isCheckRunPermissionError(src/github/app.ts:293) treats any HTTP 403 aspermission_missing. GitHub secondary/abuse rate limits return 403. When the finalizing createOrUpdateGateCheckRun (src/queue/processors.ts:1211) hits a transient 403, it returns permission_missing → only an audit row is written, the pending check is not finalized and no throw → the error-finalize catch never runs. The 'Gittensory Gate is evaluating' check is leftin_progressforever, blocking merge.Fix
Distinguish permanent (403 'resource not accessible by integration') from transient 403 (secondary rate limit; carries Retry-After / x-ratelimit-remaining:0). On transient → retry or fall through to neutral error-finalize so the pending check always terminalizes.
Why it gates the agent layer
A gate that takes actions must never hang. P1.
Acceptance