LearnNewsExamplesServices
Frontmatter
title>-
authorneo-opus-ada
stateMerged
createdAtAug 15, 2026, 11:26 AM
updatedAtAug 15, 2026, 2:53 PM
closedAtAug 15, 2026, 2:53 PM
mergedAtAug 15, 2026, 2:53 PM
branchesdev ← claude/devindex-optin-isolation-a59f06
urlhttps://github.com/neomjs/neo/pull/17160
contentTrust
projected
quarantined0
signals[]
Merged
neo-opus-ada
neo-opus-ada commented on Aug 15, 2026, 11:26 AM

Resolves #17150

Closes the DevIndex half of the 2026-08-14 data-sync incident. #17149 stopped an intake denial from freezing corpus publication; this restores the intake itself and stops one dead phase from taking a working one with it.

Two independent defects, both surfaced by the same GitHub change

1. A dead phase killed a working one. OptIn.run() has two intake mechanisms reading the same repository over different connections — stargazers and issues. The stargazer loop ran first and threw straight out of run(), so processIssues() was never reached and opt-in intake went to zero rather than halving. The repository-not-found branch had the same defect for the same reason: it returned out of run(), and processIssues already handles NOT_FOUND for itself, so pre-empting it bought nothing.

2. The stargazer read needed a permission nobody had identified. GitHub limited the endpoint to repository admins and collaborators and infers that status from contents-write.

The permission finding, measured rather than argued

Four preflight_only probes, one variable each, each mutating nothing:

requested intake permissions devindex-opt-in.stargazers run
issues:write, metadata:read (shipped) DENIED 31870344604
+ administration:read DENIED 31874111382
+ contents: write reachable ✅ 31875177082
+ contents: read DENIED 31875769230

Read does not suffice; there is no lesser lever. devindex-opt-out.issues stayed reachable in all four, which rules out a sick token and isolates the finding to the connection.

These results are only trustworthy because #17148 merged first. The old preflight probed repository{id}, which resolves from metadata and would have printed reachable in all four rows. The instrument had to be fixed before the experiment could mean anything — that is the whole reason the probe change was worth shipping separately.

The cost, stated rather than buried

The workflow's previous note claimed this token "must never be able to write code anywhere". That is no longer true, and the comment now says so instead of quietly contradicting itself.

repo contents size
neomjs/devindex-opt-in .github/, .gitignore, README.md 3 KB
neomjs/devindex-opt-out same shape 1 KB

No code, no data. The DevIndex corpus lives in neomjs/neo under apps/devindex/resources/data/, which this App has no installation on and still cannot reach. The #15744 property that matters — the intake identity cannot publish to this repository — is intact.

Operational coupling — corrected at f5ce78fe4a, and the original claim is retained here struck through because it was wrong in the direction that raises alarm:

create-github-app-token fails the step outright when asked for a permission the installation lacks, so a cleanup that drops contents takes the pipeline down before it can emit a corpus.

The claim above is TRUE. I briefly "corrected" it into a falsehood and have withdrawn that — recording the round trip because the mechanism is worth more than the conclusion.

I asserted this action does not validate requested permissions, citing run 31877595849 as a success. It was not a success: that run logs ##[error]The permissions requested are not granted to this installation (HTTP 422). I had read conclusion from the jobs API — the field that #17162's continue-on-error guard, present on that probe branch, rewrites to success on a failed step while outcome retains the failure. Check a mint by reading its log, never its step status. Caught by @neo-gpt.

The coupling is real and it has teeth in both directions. This line and the App grant must move together, and getting it wrong is worse than losing the stargazer read: create-github-app-token fails the mint outright — HTTP 422, "The permissions requested are not granted to this installation" (run 31877595849). @tobiu: administration and organization administration are safe to revoke whenever; contents is not — dropping it while this line remains fails the mint, which without #17162's guard kills the job before a corpus byte exists. That is the original warning, restored.

What a successful mint does and does not prove — the distinction, narrowed. It proves the requested grant was available to the action: create-github-app-token fails when a requested permission is not granted (HTTP 422) or the named repository has no installation (404), so a clean mint means neither happened. It does not prove the endpoint capability — that the token can actually perform the read the stage needs. Only the direct stargazers probe settles that, which is why #17150's matrix rests on the capability observation rather than on the mint. Both halves matter here: the mint result validates the credential topology, the probe validates the capability, and conflating them is what produced this PR's correction round-trip.

Deltas

  • apps/devindex/services/OptIn.mjs — stargazer phase extracted to collectStargazerOptIns, which returns its own result rather than mutating run() state; run() isolates it, continues to the issue path, and rethrows last; NOT_FOUND return → break. Also strips pre-existing trailing whitespace (12 lines) and applies block-alignment, both forced by whole-staged-file hooks once the loop moved.
  • .github/workflows/data-sync-pipeline.yml — permission-contents: write on the intake mint, with the probe matrix and the cost recorded inline.
  • test/playwright/unit/app/devindex/OptInPhaseIsolation.spec.mjs — new, 4 tests.

Rejected

  • Adding the denial string to OptIn.mjs's NOT_FOUND skip branch. One line, turns everything green, and converts a real permission regression into a silent no-op — the intake would rot undetected. The deferral keeps the failure loud.
  • Retiring the star path (this ticket's option A). It was the right plan while option B looked falsified; the contents: write probe reopened it, and one-click opt-in is materially lower friction than "file an issue from a template" for a community-facing index.
  • administration: read. Tested first on the theory that "admins and collaborators" maps to an administration permission. It does not — probe 2 above.

Evidence: L2 (unit specs) + L4 (four live preflight_only runs against the real installation, cited above) → L4 obtained for the permission claim, which is the one no unit test can reach. Residual: none — the deferred-behaviour claims are unit-verified and the access claim is verified against production credentials.

Test Evidence

npx playwright test -c test/playwright/playwright.config.unit.mjs \
  test/playwright/unit/app/devindex/
  2633 passed (11.0s)

New coverage, each pinning a distinct half:

  • a denied stargazer read no longer prevents the issue path from running — the exact production denial; asserts the issue phase is reached and that its opt-ins reach the tracker, so the surviving mechanism genuinely still admits users.
  • the run still FAILS after the issue path completes — isolation is not suppression — pins the ordering, not just the outcome. A rethrow placed earlier would restore the original defect while reporting the same error.
  • a missing opt-in repository ends the stargazer phase without pre-empting the issue phase.
  • collectStargazerOptIns reports the phase result rather than mutating run() state.

Every test was verified RED against the pre-fix source individually. A single suite run reported 1 failed — which looks like three vacuous tests but was describe.serial skipping after the first failure (3 did not run). Run with -g, all four fail against the old code. Flagging the trap because its inverse is worse: had test 1 been the failing one and 2–4 genuinely vacuous, that same summary would have rubber-stamped them.

Post-Merge Validation

Observable only on a scheduled dev run, which cannot exist before merge. Owned by the standing alarm rather than this PR's close target: #17131 is refreshed on every breach evaluation and is still there when these become checkable.

Residual-Owner: #17131

  • The first scheduled dev run after merge reports neomjs/devindex-opt-in reachable (OptIn stargazer read) in preflight — the restriction is satisfied by the shipped permission set, not just by a dispatch probe.
  • [OptIn] completes both phases in the same run, and #17131 closes on the recovery evaluation.
  • A subsequent run publishes a corpus commit with no deferred intake failure.

⚖️ Ada · @neo-opus-ada · Claude Opus 5 · Claude Code

tobiu
tobiu APPROVED reviewed on Aug 15, 2026, 2:53 PM

No review body provided.