The project-board steps in issue-triage.yml and openspec-sync.yml are disabled behind a PROJECTS_ENABLED = false constant. This issue tracks restoring them.
Why they were disabled
PROJECT_TOKEN was a personal access token and it died on or about 2026-08-04 — HTTP 401 on GET /user. That endpoint is an authentication probe, so the failure is revocation or expiry; re-scoping cannot address it. It was also stored per-repository in only 8 of the 18 apps; the other 10 received an empty value and failed on a different branch of the same root cause.
Every workflow run already gets GITHUB_TOKEN, which cannot expire or be revoked out from under the fleet, and which can do all of the issue work in both files — create, update, label, assign, comment. What it cannot do is ProjectsV2: it is scoped to a single repository and carries no project scope, and the board here is an organisation project (organization(login:…){ projectV2(number:…) }). orgs.checkMembershipForUser is likewise unavailable to it, though that call was already wrapped in a try/catch that degrades to source = 'community'.
So the split is: keep the issue automation working today on a credential that cannot rot, and switch the board steps back on when a project-scoped credential exists.
What is disabled
| file |
site |
what still happens |
what does not |
issue-triage.yml |
addToProject() |
label, assign, welcome comment |
board placement, App/Source fields |
issue-triage.yml |
triage:accept-* |
acceptance comment, label removal |
Status → Proposal |
issue-triage.yml |
backlog-triage |
label + assign every untriaged issue |
board placement |
openspec-sync.yml |
per-change loop |
issue created / updated / closed |
board placement, Status/App/Change Name/Source |
Every disabled site emits a core.notice naming what did not happen. They do not pass silently — a step that stops doing its work while still reporting success is the failure mode this repository exists to prevent.
One user-visible copy change came with it: the triage welcome comment no longer tells the contributor their issue "has been added to our triage board", because while the board step is off that would be false. A bot claiming work it did not do is worse than a bot saying less.
To restore
A project-scoped token cannot simply replace github-token:. These scripts do repo issue work and project work, and actions/github-script gives one authenticated client. Restoring needs:
- A project-scoped credential — preferably a GitHub App installed org-wide with Organization → Projects: Read and write, minting an installation token per run via
actions/create-github-app-token. An App has no expiry to rotate, covers all 18 repos from one install (removing the present-in-8-absent-in-10 split structurally), and states its permissions explicitly rather than a classic PAT's blanket repo. A PAT with repo + read:org + project published as an org secret is the lighter alternative.
- A second authenticated client in each script for the GraphQL calls, leaving
github as the GITHUB_TOKEN client for issues.
- Flip
PROJECTS_ENABLED to true at all 4 sites (12 references in issue-triage.yml, 6 in openspec-sync.yml).
Verify the restore, do not assume it
The old preflight probed GET /user, which any live token answers 200 — so it proved authentication and said nothing about the project scope. A restore is verified by a board mutation succeeding, not by a preflight passing.
Also worth knowing before judging the result: OpenSpec Sync has never succeeded on any repo, so there is no known-good state to return to. Issue Triage has (openregister has 131 successes), so that one has a baseline.
The project-board steps in
issue-triage.ymlandopenspec-sync.ymlare disabled behind aPROJECTS_ENABLED = falseconstant. This issue tracks restoring them.Why they were disabled
PROJECT_TOKENwas a personal access token and it died on or about 2026-08-04 — HTTP 401 onGET /user. That endpoint is an authentication probe, so the failure is revocation or expiry; re-scoping cannot address it. It was also stored per-repository in only 8 of the 18 apps; the other 10 received an empty value and failed on a different branch of the same root cause.Every workflow run already gets
GITHUB_TOKEN, which cannot expire or be revoked out from under the fleet, and which can do all of the issue work in both files — create, update, label, assign, comment. What it cannot do is ProjectsV2: it is scoped to a single repository and carries noprojectscope, and the board here is an organisation project (organization(login:…){ projectV2(number:…) }).orgs.checkMembershipForUseris likewise unavailable to it, though that call was already wrapped in atry/catchthat degrades tosource = 'community'.So the split is: keep the issue automation working today on a credential that cannot rot, and switch the board steps back on when a project-scoped credential exists.
What is disabled
issue-triage.ymladdToProject()issue-triage.ymltriage:accept-*issue-triage.ymlbacklog-triageopenspec-sync.ymlEvery disabled site emits a
core.noticenaming what did not happen. They do not pass silently — a step that stops doing its work while still reporting success is the failure mode this repository exists to prevent.One user-visible copy change came with it: the triage welcome comment no longer tells the contributor their issue "has been added to our triage board", because while the board step is off that would be false. A bot claiming work it did not do is worse than a bot saying less.
To restore
A project-scoped token cannot simply replace
github-token:. These scripts do repo issue work and project work, andactions/github-scriptgives one authenticated client. Restoring needs:actions/create-github-app-token. An App has no expiry to rotate, covers all 18 repos from one install (removing the present-in-8-absent-in-10 split structurally), and states its permissions explicitly rather than a classic PAT's blanketrepo. A PAT withrepo+read:org+projectpublished as an org secret is the lighter alternative.githubas theGITHUB_TOKENclient for issues.PROJECTS_ENABLEDtotrueat all 4 sites (12 references inissue-triage.yml, 6 inopenspec-sync.yml).Verify the restore, do not assume it
The old preflight probed
GET /user, which any live token answers 200 — so it proved authentication and said nothing about theprojectscope. A restore is verified by a board mutation succeeding, not by a preflight passing.Also worth knowing before judging the result:
OpenSpec Synchas never succeeded on any repo, so there is no known-good state to return to.Issue Triagehas (openregister has 131 successes), so that one has a baseline.