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):
- Stargazers —
// 1. Check Stargazers, the paged stargazers GraphQL loop at :49-113. Dead by policy.
- Issues —
// 2. Check Issues → processIssues() 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:114 — processIssues(), unreachable behind it.
apps/devindex/services/GitHub.mjs:658 — where the GraphQL error is raised.
buildScripts/dataSyncPreflight.mjs:33-36 — REQUIRED_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
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"
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
intakeApp between12:05:23Zand13:19:19Zon 2026-08-14 — the last-green → first-red boundary of#17131.Verified live, and it tracks collaborator status rather than repository visibility:
neomjs/neo(collaborator)devindex-opt-in(pullonly)devindex-opt-out(pullonly)/stargazersstargazersedges: []stargazers_count: 16)Under the
intakeApp token the GraphQL connection answersResource 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. Check Stargazers, the pagedstargazersGraphQL loop at:49-113. Dead by policy.// 2. Check Issues→processIssues()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:78already skips gracefully onNOT_FOUND/Could not resolve. A non-collaborator REST call returns 404 (which that branch would absorb), but the GraphQL connection returnsResource 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;:76the throwing call;:77-83the existing NOT_FOUND skip.apps/devindex/services/OptIn.mjs:114—processIssues(), unreachable behind it.apps/devindex/services/GitHub.mjs:658— where the GraphQL error is raised.buildScripts/dataSyncPreflight.mjs:33-36—REQUIRED_REPOSITORIESstill declaresconnection: 'stargazers'fordevindex-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_onlyprobes, one variable each, none mutating anything:devindex-opt-in.stargazersissues:write,metadata:read(as shipped)administration:readcontents:writecontents:readRead does not suffice and there is no lesser lever.
devindex-opt-out.issuesstayed 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'scontinue-on-errorguard, 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: readfailed 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: writethen 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
#17148merged first. The old preflight probedrepository{id}, which resolves from metadata and would have printedreachablein 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
intakeApp collaborator/admin-equivalent access onneomjs/devindex-opt-inandneomjs/devindex-opt-out.A (fallback). Retire the star path. Delete the stargazer loop, keep issue-based opt-in as the sole mechanism, set
REQUIRED_REPOSITORIES' connection fordevindex-opt-intoissues, 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=trueverifies 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
processIssues()from running; issue-based opt-in works while the star path is unavailable.REQUIRED_REPOSITORIES' declaredconnectionfordevindex-opt-inmatches whatever read the code actually performs after the decision.Out of Scope
#17148/ PR #17149.Avoided Traps
NOT_FOUNDskip 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.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"