Skip to content

date: 2026-06-08 tags: [github, mcp, projects-v2, web-env, automation] status: active graduated_to:

GitHub MCP expired? $GH_TOKEN curl still writes — and Projects v2 field-sets need the issue on the board first

Symptom — mid-session the GitHub MCP server returned "requires re-authorization (token expired)", so mcp__github__* reads/writes failed. A whole backlog reshape (milestones, sub-issues, types, triage Status, labels) still needed doing.

Root cause — the MCP's OAuth and the ambient $GH_TOKEN are independent credentials. The MCP expiring doesn't revoke $GH_TOKEN, which (here) has repo admin — so REST + GraphQL via curl kept working for the whole reshape.

Fix / how — drive GitHub directly with curl -H "Authorization: Bearer $GH_TOKEN":

  • milestone / native type: PATCH /issues/{n} {"milestone":N,"type":"Task"}
  • sub-issue link / reparent: POST /issues/{parent}/sub_issues {"sub_issue_id":<child DB id>} (remove: DELETE …/sub_issue)
  • labels: POST /issues/{n}/labels
  • Projects v2 Status: GraphQL updateProjectV2ItemFieldValue (see 2026-06-06-projects-v2-status-via-graphql.md).

Gotcha that bit: an issue must be on the board before its Status can be set — only 36 of 88 Foundational issues were. addProjectV2ItemById(projectId, contentId:<node_id>) is idempotent (returns the existing item if already added), so the safe pattern is add-then-set for every issue, unconditionally.

Guard — convention, no test. When the GitHub MCP is unavailable in the web env, fall back to $GH_TOKEN curl rather than declaring GitHub unreachable; and always addProjectV2ItemById before an updateProjectV2ItemFieldValue. (Caveat: $GH_TOKEN scope can still vary — it 403'd on the checks API this session even while issues/projects writes worked.)