Skip to content

fix(classifier): harden the README probe's error handling#55

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

fix(classifier): harden the README probe's error handling#55
F-e-u-e-r merged 1 commit into
mainfrom
fix/readme-probe-hardening

Conversation

@F-e-u-e-r

Copy link
Copy Markdown
Owner

Summary

Follow-up hardening to the vanished-star fix (#54), driven by a three-round multi-model cross review (Codex GPT xhigh + Grok 4.5, iterated until both returned "No findings"). The GraphQL blob-OID README probe now handles malformed and transient GitHub responses deterministically, and errors carry honest exit semantics. planner.ts is unchanged — this is entirely in the probe seam, classifyError, and the CLI's error exit code.

Changes

  • isRepositoryNotFound (readme-source.ts) — swallow a GraphQL NOT_FOUND as "repo gone" only when every error is rooted at exactly the repository field (path: ['repository'], or an absent path defensively). A mixed array, a nested path (['repository','object']), or a different-field NOT_FOUND now propagates instead of being masked.
  • Empty / malformed envelope (readme-source.ts) — a data-less 2xx body (raw @octokit/graphql yields undefined), {}, or [] lacks the repository field and is a transient glitch, not an absent README: it retries via the shared RetryCoordinator (matching github-client's requireResponse contract) instead of silently downgrading.
  • classifyError (github-client/retry.ts) — HTTP 500 → retryable alongside 502/503/504 (a malformed request is already 400/422 → terminal).
  • fatal() (cli.ts) — honors ExporterError.exitCode, so a DeferredError (a probe that exhausted its bounded retries, a rate-limit cooldown) exits 20"defer this run, keep last-known-good, retry next schedule" — instead of masquerading as a terminal exit 10.

Design policy (chosen after review)

A transient blip is absorbed by bounded retries; a sustained or persistent fault defers the whole run (exit 20, keep last-known-good, retry next scheduled run) rather than degrading a repo to metadata. An earlier attempt to degrade-to-metadata / fall through to REST per repo was removed because the review showed it overwrites good README annotations and is rejected by the provenance gate.

Verification

  • End-to-end against real GitHub: the deleted repo (curtischoutw/claude-institution) returns null; a live repo resolves normally.
  • New/changed tests lock: exact-['repository'] matching, mixed/nested/other-field propagation, path-less tolerance, empty/malformed-envelope retry, HTTP-500 retry, and DeferredError-on-exhaustion. Full repo suite: 699 tests green; format:check / lint / typecheck clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YLoMBRETP2VWBxT1whUNc9

Follow-up to the vanished-star fix (#54), from a multi-model cross review
(Codex GPT xhigh + Grok 4.5, three rounds to no findings). The GraphQL
blob-OID probe now handles malformed and transient GitHub responses
deterministically, and errors carry honest exit semantics:

- isRepositoryNotFound swallows a GraphQL NOT_FOUND only when EVERY error
  is rooted at exactly the `repository` field (path ['repository'] or
  absent). A mixed array, a nested path, or a NOT_FOUND on another field
  now propagates instead of being masked as "repo gone".
- A data-less 2xx envelope (raw @octokit/graphql yields undefined) or a
  record lacking the `repository` field is a transient glitch, not an
  absent README: it retries via the shared coordinator (github-client's
  requireResponse contract) instead of silently downgrading to REST.
- classifyError treats HTTP 500 as retryable alongside 502/503/504 (a
  malformed request is already 400/422 -> terminal).
- cli.ts fatal() honors ExporterError.exitCode, so a DeferredError (a
  probe that exhausted its bounded retries, a rate-limit cooldown) exits
  20 -- "defer this run, keep last-known-good, retry next schedule" --
  instead of masquerading as a terminal exit 10.

Policy: a transient blip is absorbed by bounded retries; a sustained or
persistent fault defers the whole run (exit 20) rather than degrading a
repo to metadata, which would overwrite a good README annotation and be
rejected by the provenance gate. The planner is unchanged.

Verified end-to-end against the real deleted repo (returns null) and a
live repo. Full suite: 699 tests green.

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 1e98377 into main Jul 11, 2026
4 checks passed
@F-e-u-e-r F-e-u-e-r deleted the fix/readme-probe-hardening branch July 11, 2026 18:29
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