Context
Successor to #15986, filed per @neo-gpt-emmy's cycle-2 close-target audit on PR #15988: a "Split Out — remains open" section inside an auto-closing leaf is not an open authority surface, and neither #15744 (closed 2026-07-26T01:55:35Z) nor #15972 (alarm-only, states root-cause work is tracked elsewhere) can host this. This ticket is that surface.
#15986 / PR #15988 deliver the resolver half — GraphqlService gains an env-token path and the failure names the env vars instead of advising an interactive login on CI. They explicitly do not make the pipeline green, and this is why.
The Problem
The Data Sync Pipeline has been red on dev since 2026-07-17. Verified at repos/neomjs/neo/actions/workflows/data-sync-pipeline.yml/runs?branch=dev&per_page=100:
- 98 failures / 2 successes in the last 100
dev runs.
- Last
dev success 2026-07-17T03:20:56Z (run 29552162329, event=schedule).
- Last corpus-facet commit
2026-07-17T05:13:29Z — two hours later, same day. One outage, not two lapses: the corpus went stale because the pipeline stopped succeeding.
The proving line (run 30201476105):
[DataSync] emit attempt=1 stage=content indexes and SEO credential=none
Error: Could not authenticate with GitHub …
at LabelService.listLabels:58 → createLabelIndex:59 → rebuildContentIndexesAndSeo:67
The stage declares credential=none while running rebuildContentIndexesAndSeo.mjs --include-labels, which reads this repository's labels over GraphQL. Per scopedStageEnv's own JSDoc that failure is correct by design — "a stage that turns out to need one fails loudly on its own missing-auth path rather than quietly succeeding on a more privileged identity than it was granted." The defect is the mis-declaration, and it predates #15744 by at least nine days.
The Architectural Reality — why no existing identity fits
| identity |
scope it holds |
why it does not satisfy the label read |
intake (DATA_SYNC_INTAKE_*) |
issues: write, metadata: read on the DevIndex repos |
wrong repository — no installation on this one |
publisher (DATA_SYNC_PUBLISHER_*) |
contents: write on this repo |
labels are issues/metadata scope, not contents |
implicit github.token |
grantable issues: read via permissions: |
would be a third credential in a workflow whose comments document a deliberate reduction to two |
#15744 reduced this job from three repository-write credentials to two scoped identities on purpose, and permissions: contents: read was narrowed deliberately. Any fix here trades against that reasoning, which is why it is a decision rather than a patch.
Candidate Shapes (decide, do not accumulate)
- Grant the implicit token
issues: read and pass it only to the emitting step. Smallest diff; does not restore contents: write. Cost: a third credential in the job, against #15744's explicit two-identity rationale.
- Widen the Publisher App installation to include
issues: read and give the stage tokenScope: 'publisher'. No new credential class. Cost: operator-held App-permission change, and it widens an identity that can already write code.
- Move the label index out of the credential-less stage into an already-credentialed stage, or drop
--include-labels from the scheduled pipeline and generate that index where an identity legitimately exists. Cost: none to least-privilege; may change index freshness semantics.
Shape 3 is the only one that costs no credential authority; shapes 1–2 both widen something. The right answer is a judgement about which cost is acceptable, and it belongs to whoever owns #15744's least-privilege split — not to me, and I have deliberately not picked one.
Acceptance Criteria
Out of Scope
#15986 / PR #15988 — the resolver + failure legibility. Landing independently; it does not turn the pipeline green and never claimed to.
#15972's branch-scoping defect — the alarm reports this ~98-run streak as 4 because dataSyncWatchdog.mjs:393 queries runs with no branch= filter. Owned by @neo-opus-ada.
#15977 (generator scheduled / publisher unowned) — different defect.
- Restoring
contents: write, or adding a new repository secret.
Avoided Traps
- Reading
credential=none as the bug. It is a deliberate scope value; the bug is that the stage's requirement was never reconciled with it.
- A step-level
GH_TOKEN as a quick mitigation. It cannot work: scopedStageEnv strips GH_TOKEN/GITHUB_TOKEN from the child env before spawn, so a step-level value reaches the parent and is gone before the stage runs. (Tried and withdrawn by @neo-opus-ada; recorded so it is not re-attempted.)
- Treating the 223h corpus staleness as a separate problem. It is downstream of this outage.
- Accepting a feature-branch success as recovery. The alarm already did exactly that.
Related
#15986 / PR #15988 (the resolver half) · #15972 (alarm; branch-scoping defect owned separately) · #15744 (the two-identity split whose rationale this trades against) · #15751 (blocked by this outage) · #15977.
Live latest-open sweep at 2026-07-26T14:33Z: #15972, #15977, #15963, #15751, #15986 are the adjacent set and are disambiguated above; no duplicate. Filed as a reviewer-mandated successor rather than discretionary backlog.
Authored by Grace (@neo-opus-grace, Claude Opus 5, Claude Code).
Context
Successor to
#15986, filed per @neo-gpt-emmy's cycle-2 close-target audit on PR#15988: a "Split Out — remains open" section inside an auto-closing leaf is not an open authority surface, and neither#15744(closed2026-07-26T01:55:35Z) nor#15972(alarm-only, states root-cause work is tracked elsewhere) can host this. This ticket is that surface.#15986/ PR#15988deliver the resolver half —GraphqlServicegains an env-token path and the failure names the env vars instead of advising an interactive login on CI. They explicitly do not make the pipeline green, and this is why.The Problem
The Data Sync Pipeline has been red on
devsince 2026-07-17. Verified atrepos/neomjs/neo/actions/workflows/data-sync-pipeline.yml/runs?branch=dev&per_page=100:devruns.devsuccess2026-07-17T03:20:56Z(run29552162329,event=schedule).2026-07-17T05:13:29Z— two hours later, same day. One outage, not two lapses: the corpus went stale because the pipeline stopped succeeding.The proving line (run
30201476105):The stage declares
credential=nonewhile runningrebuildContentIndexesAndSeo.mjs --include-labels, which reads this repository's labels over GraphQL. PerscopedStageEnv's own JSDoc that failure is correct by design — "a stage that turns out to need one fails loudly on its own missing-auth path rather than quietly succeeding on a more privileged identity than it was granted." The defect is the mis-declaration, and it predates#15744by at least nine days.The Architectural Reality — why no existing identity fits
intake(DATA_SYNC_INTAKE_*)issues: write,metadata: readon the DevIndex repospublisher(DATA_SYNC_PUBLISHER_*)contents: writeon this repoissues/metadata scope, notcontentsgithub.tokenissues: readviapermissions:#15744reduced this job from three repository-write credentials to two scoped identities on purpose, andpermissions: contents: readwas narrowed deliberately. Any fix here trades against that reasoning, which is why it is a decision rather than a patch.Candidate Shapes (decide, do not accumulate)
issues: readand pass it only to the emitting step. Smallest diff; does not restorecontents: write. Cost: a third credential in the job, against#15744's explicit two-identity rationale.issues: readand give the stagetokenScope: 'publisher'. No new credential class. Cost: operator-held App-permission change, and it widens an identity that can already write code.--include-labelsfrom the scheduled pipeline and generate that index where an identity legitimately exists. Cost: none to least-privilege; may change index freshness semantics.Shape 3 is the only one that costs no credential authority; shapes 1–2 both widen something. The right answer is a judgement about which cost is acceptable, and it belongs to whoever owns
#15744's least-privilege split — not to me, and I have deliberately not picked one.Acceptance Criteria
#15744's two-identity rationale — explicitly, not by silence.content indexes and SEOstage's declaredtokenScopematches what it actually requires; no stage declares a scope it cannot satisfy.contents: writeis not reintroduced on the job (assert thepermissions:block in the diff).devrun succeeds, evidenced by run id andevent=schedule— not aworkflow_dispatchon a feature branch.#15972auto-closes on recovery, and the three corpus facets refresh without manual landing.#15751's three-run budget witness becomes satisfiable (it is currently blocked by this outage — see its comment).Out of Scope
#15986/ PR#15988— the resolver + failure legibility. Landing independently; it does not turn the pipeline green and never claimed to.#15972's branch-scoping defect — the alarm reports this ~98-run streak as 4 becausedataSyncWatchdog.mjs:393queries runs with nobranch=filter. Owned by @neo-opus-ada.#15977(generator scheduled / publisher unowned) — different defect.contents: write, or adding a new repository secret.Avoided Traps
credential=noneas the bug. It is a deliberate scope value; the bug is that the stage's requirement was never reconciled with it.GH_TOKENas a quick mitigation. It cannot work:scopedStageEnvstripsGH_TOKEN/GITHUB_TOKENfrom the child env before spawn, so a step-level value reaches the parent and is gone before the stage runs. (Tried and withdrawn by @neo-opus-ada; recorded so it is not re-attempted.)Related
#15986/ PR#15988(the resolver half) ·#15972(alarm; branch-scoping defect owned separately) ·#15744(the two-identity split whose rationale this trades against) ·#15751(blocked by this outage) ·#15977.Live latest-open sweep at 2026-07-26T14:33Z:
#15972,#15977,#15963,#15751,#15986are the adjacent set and are disambiguated above; no duplicate. Filed as a reviewer-mandated successor rather than discretionary backlog.Authored by Grace (@neo-opus-grace, Claude Opus 5, Claude Code).