LearnNewsExamplesServices
Frontmatter
id17150
titleStar-based DevIndex opt-in is dead by GitHub policy and blocks the issue path
stateClosed
labels
bugaiarchitecture
assigneesneo-opus-ada
createdAtAug 15, 2026, 9:53 AM
updatedAtAug 15, 2026, 2:53 PM
githubUrlhttps://github.com/neomjs/neo/issues/17150
authorneo-opus-ada
commentsCount2
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 15, 2026, 2:53 PM

Star-based DevIndex opt-in is dead by GitHub policy and blocks the issue path

Closed Backlog/active-chunk-16 bugaiarchitecture
neo-opus-ada
neo-opus-ada commented on Aug 15, 2026, 9:53 AM

Context

GitHub restricted the stargazers endpoint to repository admins and collaborators (changelog, 2026-06-30, titled "Upcoming", no published enforcement date). The rollout reached our intake App between 12:05:23Z and 13:19:19Z on 2026-08-14 — the last-green → first-red boundary of #17131.

Verified live, and it tracks collaborator status rather than repository visibility:

probe neomjs/neo (collaborator) devindex-opt-in (pull only) devindex-opt-out (pull only)
REST /stargazers 200, 3 rows 404 404
GraphQL stargazers returns edges edges: []
REST repo root 200 200 (stargazers_count: 16) 200

Under the intake App token the GraphQL connection answers Resource not accessible by integration. This is permanent and by design — not an outage that clears.

#17148 (PR #17149) fixes the blast radius: an intake denial no longer freezes corpus publication. It deliberately does not touch DevIndex behaviour, which is this ticket.

The Problem

OptIn.run() has two independent intake mechanisms (apps/devindex/services/OptIn.mjs):

  1. Stargazers// 1. Check Stargazers, the paged stargazers GraphQL loop at :49-113. Dead by policy.
  2. Issues// 2. Check IssuesprocessIssues() at :114, the issue-template opt-in. Still works.

The star loop runs first and throws, so processIssues() is never reached. A permanently-dead half is taking the working half down with it: DevIndex opt-in is currently 100% unavailable when it could be ~50% available. The last green run shows both paths executing ([OptIn] Checking for new opt-in requests... then [OptIn] Checking for new opt-in issue requests...), so this is a regression in reachability, not a missing feature.

Note the asymmetry that hid this: OptIn.mjs:78 already skips gracefully on NOT_FOUND / Could not resolve. A non-collaborator REST call returns 404 (which that branch would absorb), but the GraphQL connection returns Resource not accessible by integration — which is not in the branch, so it throws.

The Architectural Reality

  • apps/devindex/services/OptIn.mjs:49-113 — the stargazer loop; :76 the throwing call; :77-83 the existing NOT_FOUND skip.
  • apps/devindex/services/OptIn.mjs:114processIssues(), unreachable behind it.
  • apps/devindex/services/GitHub.mjs:658 — where the GraphQL error is raised.
  • buildScripts/dataSyncPreflight.mjs:33-36REQUIRED_REPOSITORIES still declares connection: 'stargazers' for devindex-opt-in (added by #17148). If the star path is retired, that entry's connection must change with it or the preflight will deny forever on a read nothing performs.

The Fix

DECIDED 2026-08-15 by @tobiu: option B — restore access. Implemented in PR #17160 via contents: write, which is the permission GitHub infers collaborator status from.

Four preflight_only probes, one variable each, none mutating anything:

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

Read does not suffice and 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.

All four mint steps ran clean — zero errors in their logs — and all four predate #17162's continue-on-error guard, so their green is genuine rather than guard-rewritten and the grants were live at probe time. Verified by direct log inspection, not inferred from the step status, because that inference failed elsewhere on this ticket the same day.

One wrong turn, recorded because the mechanism generalises. After administration: read failed I concluded no App permission could satisfy an account-status restriction. That was an over-generalisation from a single negative, and @tobiu challenged it; contents: write then worked. GitHub's own docs list App installation tokens as supported for this endpoint, which should have stopped the claim before it was made.

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.

Option B is the work and it is delivered in PR #17160. Option A below is retained as the fallback that turned out not to be needed.

B (CHOSEN). Restore access. Grant the intake App collaborator/admin-equivalent access on neomjs/devindex-opt-in and neomjs/devindex-opt-out.

The grant alone will NOT reach the pipeline — there are TWO places, and both must agree. .github/workflows/data-sync-pipeline.yml:96-97 mints the intake token with an EXPLICIT permission list:

permission-issues: write
permission-metadata: read

actions/create-github-app-token@v3 mints exactly what is requested — the workflow's own comment says so: "without this, v3 mints a token carrying every permission the installation holds". That cap is deliberate (it keeps least-privilege reviewable in the diff), but it means widening the App installation while leaving these lines unchanged produces a token that still lacks the new permission, and the failure is bit-identical to no grant at all. Whatever permission unlocks the stargazer read must be added here in the same change.

A (fallback). Retire the star path. Delete the stargazer loop, keep issue-based opt-in as the sole mechanism, set REQUIRED_REPOSITORIES' connection for devindex-opt-in to issues, and update any doc still saying "star the repo to opt in".

Verification. Note that the current preflight cannot confirm either outcome: it probes repository{id}, which resolves from metadata and passes whether or not stargazers is readable. #17148 / PR #17149 is what makes the grant verifiable — it selects the connection the entry names and requires it to resolve. With that merged, DATA_SYNC_PREFLIGHT_ONLY=true verifies the credential topology with no side effects (it skips the collection stages, so OptOut comments on and closes nothing), which is the cheap iteration loop for getting the grant right.

Either way the star loop must stop being fatal to processIssues().

Acceptance Criteria

  • A denied stargazer read no longer prevents processIssues() from running; issue-based opt-in works while the star path is unavailable.
  • The unavailability is surfaced (logged//counted), not silently swallowed — a carve-out that quiets the guard would let the whole intake rot undetected.
  • REQUIRED_REPOSITORIES' declared connection for devindex-opt-in matches whatever read the code actually performs after the decision.
  • If the star path is retired, no user-facing doc still instructs "star to opt in".
  • Unit coverage: stargazer denial ⇒ issue path still runs and its opt-ins are recorded.

Out of Scope

  • The pipeline blast-radius fix — #17148 / PR #17149.
  • The GitHub-side restriction itself; it is not appealable by us.

Avoided Traps

  • Adding the denial string to the NOT_FOUND skip branch and calling it done. One line, turns everything green, and converts a permanent capability loss into a silent no-op — the opt-in feature would rot with nobody noticing. The loss must be visible.
  • Assuming the whole opt-in feature is dead. Only the star half is; processIssues() is intact and was working in the last green run.

Related

  • #17148 / PR #17149 — the pipeline-side blast-radius fix that keeps the corpus publishing.
  • #17131 — the standing alarm, which stays red until this or a credential change resolves the intake.
  • #15744 — the two-identity least-privilege split that option B would reverse.

Retrieval Hint: "GitHub stargazers restricted to collaborators; DevIndex star opt-in dead, issue path still works"

tobiu referenced in commit c03f3a5 - "fix(devindex): restore the stargazer read and stop a dead phase killing the working one (#17150) (#17160) on Aug 15, 2026, 2:53 PM
tobiu closed this issue on Aug 15, 2026, 2:53 PM
tobiu referenced in commit 6772ea0 - "fix(build): an intake credential fault no longer freezes corpus publication (#17162) (#17165) on Aug 15, 2026, 2:54 PM