LearnNewsExamplesServices
Frontmatter
id15359
titleData Sync Pipeline dies on a transient GraphQL error: `Resource not accessible by integration` is not retried (the GraphQL half of #15328)
stateClosed
labels
bugaibuild
assigneesneo-opus-ada
createdAtJul 17, 2026, 4:12 PM
updatedAtJul 18, 2026, 1:00 PM
githubUrlhttps://github.com/neomjs/neo/issues/15359
authorneo-opus-grace
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 18, 2026, 1:00 PM

Data Sync Pipeline dies on a transient GraphQL error: Resource not accessible by integration is not retried (the GraphQL half of #15328)

Closed Backlog/active-chunk-7 bugaibuild
neo-opus-grace
neo-opus-grace commented on Jul 17, 2026, 4:12 PM

Premise

@tobiu reported run 29585619400 as a new regression:

[OptIn] Checking for new opt-in requests...
[GitHub] [OptIn Stars] GraphQL Query Failed: GraphQL Query Errors: Resource not accessible by integration
Error: Process completed with exit code 1.

It is not a regression, and the error message is lying about what it is. Evidence below.

Proof that this is transient, not a permissions misconfiguration

Same commit 635164bd, same token config, same query — four hours apart:

09:13 → run 29569220823, Run DevIndex Opt-In = success

09:14:29.360  [OptIn] Checking for new opt-in requests...
09:14:29.830  [OptIn] Checking for new opt-in issue requests...   ← the stargazers query PASSED
09:14:29.975  [OptIn] No new opt-in requests found.

13:51 → run 29585619400, same step = failure

13:52:32.400  [OptIn] Checking for new opt-in requests...
13:52:32.708  [GitHub] [OptIn Stars] GraphQL Query Failed: ... Resource not accessible by integration

If GITHUB_TOKEN structurally could not read neomjs/devindex-opt-in, 09:13 would have failed identically. It did not. So GitHub intermittently returns Resource not accessible by integration for a query it otherwise permits — a transient condition wearing a permissions error's costume. (neomjs/devindex-opt-in verified PUBLIC, not archived, 16 stargazers.)

Exonerated, so nobody re-audits them:

  • #15336 / #15328 (Emmy) — landed 08:29; Opt-In succeeded at 09:13, after it. It also only touched the REST path; this fails on GraphQL.
  • The code scanning merge protection ruleset — created 07:49; Opt-In succeeded at 09:13, after it.

The actual bug: the GraphQL transport treats a transient error as fatal

apps/devindex/services/GitHub.mjsquery() is called as GitHub.query(query, variables, 3, 'OptIn Stars'), i.e. with 3 retries available. It never used one. The log shows GraphQL Query Failed exactly once.

The classification has no branch for this error class:

  • fatal, no retry: Could not resolve to a User, NOT_FOUND, GraphQL Fatal Error
  • retry: body-gateway errors (502/504), or messages containing fetch / network / terminated
  • everything else → straight to throw new Error('GraphQL Query Errors: ...')

Resource not accessible by integration matches none of those tokens, so it throws on attempt 1. OptIn.run (OptIn.mjs:76) only swallows NOT_FOUND / Could not resolve, rethrows the rest — and the step exits 1, failing the whole pipeline.

This is the GraphQL half of #15328. That ticket gave the REST path a real transient story: restRetryableHttpStatuses: [429, 502, 503, 504], exponential backoff, jitter, retry-after honouring. The GraphQL path still has the old narrow substring matching. Same failure class, one transport got the fix. #15328 is closed and its owner's family is currently dark, so this is a fresh ticket rather than a reopen.

Wider signal — this pipeline is broadly unhealthy, and that is the bigger story

The Opt-In failure is not one bad step. Recent Data Sync Pipeline runs, failing at four different steps:

run when failed step
29585619400 07-17 13:51 Run DevIndex Opt-In
29576742831 07-17 11:24 Run DevIndex Opt-In
29569220823 07-17 09:13 Rebuild Indexes & SEO
29559832086 07-17 06:22 Commit, Rebase and Push
29541194846 07-16 23:02 Run DevIndex Spider
29527676442 07-16 19:21 Rebuild Indexes & SEO
29479895299 07-16 07:25 Commit, Rebase and Push

~50% of runs since 07-16 have failed, each at a different step. That is not one regression; it is a pipeline with no transient story on any of its GitHub-touching steps. And it is all-or-nothing: at 09:13, Opt-In/Opt-Out/Spider all succeeded, Rebuild Indexes & SEO failed, and Commit, Rebase and Push was skipped — so hours of successful work synced nothing. This matters more than it looks: per AGENTS.md §critical_gates, the data-sync pipeline is the one sanctioned exception to "no direct push to dev". When it silently fails half the time, portal data quietly drifts from truth and nobody is paged.

Scope

In: give the GraphQL path the same transient story #15328 gave REST — a shared, explicit retryable-classification (bounded, backoff+jitter), with Resource not accessible by integration in it. A bounded retry is correct for both readings: transient → self-heals; a genuine permission misconfig → still fails loudly, ~6s later, with the same message.

Out (surface, don't bundle): the pipeline's all-or-nothing shape and the other three failing steps. Worth its own ticket once this lands and we can see what remains — I am not inferring their root causes from this one.

Acceptance Criteria

  • Resource not accessible by integration on GraphQL is retried (bounded) rather than fatal on attempt 1.
  • REST and GraphQL classify transient failures from one shared source of truth — not two hand-maintained substring lists that drift. (#15328 built the REST half; do not fork a second policy.)
  • Red-proven: a test forces the error and asserts the transport retries then succeeds, and asserts a persistent one still fails after N attempts. Both directions — a retry that can never give up is a hang, not a fix.
  • A genuinely fatal class (NOT_FOUND, Could not resolve to a User) still fails fast — no retry storm on real misconfiguration.
  • The 3 retries OptIn already passes in are actually reachable for this class.

Avoided Traps

  • Do not "fix" this by widening the workflow's permissions: block. The 09:13 success proves the current scope is sufficient. Widening a token's scope to paper over a transient error grants standing privilege for a problem that is not about privilege.
  • Do not substring-match 'Resource not accessible' and call it done. That is the pattern that produced this gap: a hand-maintained token list on GraphQL that #15328 already replaced with real classification on REST. Unify, don't extend the thing that failed.
  • Do not assume the other three failing steps share this root cause. Different steps, unverified; naming them here is a signal, not a diagnosis.

Retrieval Hint: "devindex data sync pipeline Resource not accessible by integration GraphQL retry transient OptIn stargazers GitHub.mjs query classification 15328 REST half"