Skip to content

fix(classifier): survive a vanished starred repo in the README probe#54

Merged
F-e-u-e-r merged 1 commit into
mainfrom
fix/readme-probe-vanished-repo-resilience
Jul 11, 2026
Merged

fix(classifier): survive a vanished starred repo in the README probe#54
F-e-u-e-r merged 1 commit into
mainfrom
fix/readme-probe-vanished-repo-resilience

Conversation

@F-e-u-e-r

Copy link
Copy Markdown
Owner

Problem

The scheduled AI operational state workflow (ai-state.ymlclassifier plan --save-state) has been failing daily with exit 10:

fatal (exit 10): Request failed due to following response errors:
 - Could not resolve to a Repository with the name 'curtischoutw/claude-institution'.

curtischoutw/claude-institution is a starred repo that was deleted (its node_id no longer resolves). The operational state on starledger-ai-state records readme_path: "README.md" for it, so the planner takes the GraphQL blobOid fast path. Raw @octokit/graphql throws a NOT_FOUND GraphqlResponseError that blobOid never caught, so it propagated through the planner's per-repo sweep (planner.ts:205, no try/catch) to fatal()one vanished star aborted the entire daily state reconciliation for every repo.

It surfaced only in steady state: first discovery uses REST (404 → null, already resilient); once a README path is cached, the run takes the un-guarded GraphQL fast path.

Fix (at the seam — covers both the ai-state plan and the executor's plan)

  • blobOid treats a repository-level NOT_FOUND (deleted / renamed away / turned private) as an absent README (null), matching the REST path's existing 404 → null. isRepositoryNotFound matches errors[].type === 'NOT_FOUND' scoped to the repository field (BLOB_OID_QUERY has a single resolvable node).
  • The GraphQL probe is wrapped in the shared RetryCoordinator, so a transient fault (secondary rate limit, 5xx, GraphQL exec timeout) on any single probe retries with bounded backoff instead of crashing the batch. Auth/invalid errors still fail loudly. provenance / meta-rebase do not use the knownPath fast path, so their behaviour is unchanged.
  • Optional chaining tolerates the rare empty 2xx envelope where raw @octokit/graphql yields undefined.

Verification

  • 7 new tests: NOT_FOUND tolerated without retry, transient retry-then-succeed, GraphQL exec-timeout retry, budget-exhaustion fails loud, terminal error propagates, empty-envelope falls through to REST, non-repository NOT_FOUND is not swallowed. The regression gate was confirmed to FAIL on the pre-fix code (real TypeError).
  • End-to-end against real GitHub: the deleted repo now returns null (no crash); a live repo resolves normally.
  • Full repo suite: 694 tests green; format:check / lint / typecheck clean.

Reviewed

Diff reviewed by two independent fresh-context critics (Fable 5 + Opus). Fable caught — and this PR fixes — a data === null regression that missed the empty-envelope undefined case. One residual boundary was consciously accepted: a persistent per-repo transient failure (≥ retry budget) or a systemic outage still fails the run loudly and retries next schedule, rather than silently degrading every repo to metadata; planner-level circuit-breaking is left as a follow-up.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YLoMBRETP2VWBxT1whUNc9

The scheduled "AI operational state" workflow (classifier plan
--save-state) crashed with exit 10 when a starred repo was deleted. The
operational state records a README path for such a repo, so the planner
takes the GraphQL blobOid fast path, and raw @octokit/graphql throws a
NOT_FOUND GraphqlResponseError that blobOid never caught — one vanished
star aborted the whole per-repo sweep, stalling state reconciliation for
every repo. It surfaced only in steady state: first discovery uses REST
(404 -> null, resilient); a cached README path takes the unguarded
GraphQL fast path.

Handle a repository-level NOT_FOUND at the seam. blobOid now treats an
unresolvable repository (deleted / renamed away / turned private) as an
absent README (null), matching the REST path's existing 404 -> null. The
GraphQL probe is also wrapped in the shared RetryCoordinator, so a
transient fault (secondary rate limit, 5xx, GraphQL exec timeout) on any
single probe retries with bounded backoff instead of crashing the batch,
while auth/invalid errors still fail loudly.

- isRepositoryNotFound matches errors[].type === 'NOT_FOUND' scoped to
  the `repository` field (BLOB_OID_QUERY has one resolvable node).
- Optional chaining tolerates the rare empty 2xx envelope (undefined).
- Tests: NOT_FOUND tolerated without retry, transient retry-then-succeed,
  exec-timeout retry, budget exhaustion fails loud, terminal error
  propagates, empty envelope falls through, non-repository NOT_FOUND is
  not swallowed. Regression gate confirmed to FAIL on the pre-fix code.

Verified end-to-end against the real deleted repo (returns null) and a
live repo (resolves normally).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YLoMBRETP2VWBxT1whUNc9
@F-e-u-e-r F-e-u-e-r merged commit 2748e60 into main Jul 11, 2026
4 checks passed
@F-e-u-e-r F-e-u-e-r deleted the fix/readme-probe-vanished-repo-resilience branch July 11, 2026 12:42
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