Cloud sessions: GH_TOKEN set in environment variables is ignored for api.github.com; Dependabot alerts unreachable
Summary
In an Anthropic-hosted cloud session, a GH_TOKEN / GITHUB_TOKEN set in the cloud environment's variables is present in the container but has no effect on outbound api.github.com requests. The GitHub proxy re-authenticates every REST call with the session's own credential, so gh runs as the Claude GitHub App user-to-server identity no matter what token is set.
The docs currently state the opposite, which is what led me down a long debugging path.
Two asks, separable:
- Docs correction (small): make it explicit that a user-supplied token does not authenticate
api.github.comrequests. - Capability gap (the reason I set a token at all): security alerts are unreachable from cloud sessions.
dependabot/alertsis rejected by GitHub because the proxy's credential lacksdependabot_alerts: read, and no user-supplied credential can substitute.
Environment
- Claude Code
2.1.237, Anthropic-hosted cloud environment (Trusted network access) gh2.45.0, installed at/usr/bin/gh- Auth method: Claude GitHub App
GH_TOKEN/GITHUB_TOKENset in the environment's variables to a fine-grained PAT (github_pat_…, 93 chars). Not theproxy-injectedplaceholder — the values pass through to the container intact.
Reproduction
With a fine-grained PAT set in the environment variables, replace it inline with a syntactically invalid value:
$ GH_TOKEN=github_pat_TOTALLY_INVALID_0000 gh api user --jq '{login,type}'
{"login":"christophehurpeau","type":"User"}
An invalid token authenticates successfully. Response headers are identical for the real PAT and the garbage value:
Github-Authentication-Token-Expiration: 2026-08-20 16:46:12 UTC
X-Oauth-Scopes:
X-Accepted-Github-Permissions: allows_permissionless_access=true
Empty X-Oauth-Scopes plus a same-day expiry indicate a short-lived GitHub App user-to-server token, not the fine-grained PAT that was set.
A related symptom that makes this confusing to diagnose: gh auth status and gh api disagree, because the former validates over GraphQL (which the proxy pins to a fixed operation set) while the latter goes through the REST path.
$ gh auth status
github.com
X Failed to log in to github.com using token (GH_TOKEN)
- The token in GH_TOKEN is invalid.
$ gh api user --jq .login
christophehurpeau
Docs discrepancy
Configure cloud environments § Work with GitHub issues and pull requests says:
If you set a token, it passes through to the container unchanged, so your scripts, and GitHub's gh CLI if you install it, use it directly.
The first half is accurate — the environment variable arrives unchanged. The second half is not: gh does not use it against api.github.com. The § GitHub proxy section is closer, but scopes the caveat to GraphQL only:
The restriction applies to every request through the proxy regardless of the credentials you supply, so a GH_TOKEN you set gets the same 403.
In practice credential substitution applies to REST as well. Suggested wording: a token you set is usable by scripts that talk to other hosts, but requests to api.github.com are re-authenticated by the proxy and a user-supplied token cannot widen (or narrow) what the session can reach.
Minor, same page: it states "GitHub's gh CLI isn't pre-installed" and gives setup-script install instructions, but gh 2.45.0 is present at /usr/bin/gh in this environment.
Capability gap: security alerts
The three failure modes are distinguishable by the documentation_url in the error body, which is useful for triage:
| Layer | Signature | Endpoints observed |
| ------------------------------------ | ---------------------------------------------------- | ---------------------------------------------------------------------- |
| Proxy path allowlist | documentation_url: docs.anthropic.com/… | secret-scanning/alerts, vulnerability-alerts, user/installations |
| Proxy GraphQL pin | This GraphQL query is not enabled for this session | gh pr list and other GraphQL-backed commands |
| GitHub, using the proxy's credential | documentation_url: docs.github.com/… | dependabot/alerts |
So Dependabot alerts pass the proxy allowlist and are rejected by GitHub itself:
$ gh api "repos/OWNER/REPO/dependabot/alerts?state=open"
{"message":"Resource not accessible by integration",
"documentation_url":"https://docs.github.com/rest/dependabot/alerts#list-dependabot-alerts-for-a-repository",
"status":"403"}
Identical response with the garbage token, confirming the user-supplied PAT is not a factor.
GET /repos/{owner}/{repo}/dependabot/alerts requires dependabot_alerts: read for App tokens, or security_events for OAuth/classic tokens (community discussion #60612). The Claude GitHub App credential has neither, and because the proxy owns the Authorization header there is no supported way for a user to supply a credential that does.
Net effect: a cloud session cannot answer "what security alerts are open on this repo?" — a natural request for an agent that is otherwise well suited to triaging and fixing dependency advisories. The workaround is to leave the session and run gh api locally, or to infer the alert set indirectly from open Renovate/Dependabot PR titles and bodies, which misses any alert without an available fix.
Requests
- Correct the
GH_TOKENpassthrough wording (and theghpre-install note). - Add
dependabot_alerts: readto the Claude GitHub App's requested repository permissions, so alerts are readable when an installation grants it. - Consider
secret_scanning_alerts: readand addingsecret-scanning/alertsto the proxy path allowlist, for the same reason. Lower priority — secret scanning results are more sensitive, so gating it differently is reasonable. - Optional, cheap, and would have saved most of this investigation: have the proxy surface a distinguishable response when it substitutes a credential the caller explicitly supplied, rather than silently succeeding as a different identity.
Possibly related
- #70474 — user-supplied PAT overridden by the sandbox credential layer in routines (closed, not planned)
- #61189 — proxy OAuth credential lacks
workflowscope, blocking.github/workflows/*writes - #11078 — proxy strips
Authorization, breaking GitHub Packages npm auth - #10108 — egress proxy and GitHub subdomains