Cloud sessions: GH_TOKEN set in environment variables is ignored for api.github.com; Dependabot alerts unreachable

Status Open
Reported on v2.1.237
Maintainer reply None cached
Activity 0 comments · opened Aug 20, 2026

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:

  1. Docs correction (small): make it explicit that a user-supplied token does not authenticate api.github.com requests.
  2. Capability gap (the reason I set a token at all): security alerts are unreachable from cloud sessions. dependabot/alerts is rejected by GitHub because the proxy's credential lacks dependabot_alerts: read, and no user-supplied credential can substitute.

Environment

  • Claude Code 2.1.237, Anthropic-hosted cloud environment (Trusted network access)
  • gh 2.45.0, installed at /usr/bin/gh
  • Auth method: Claude GitHub App
  • GH_TOKEN / GITHUB_TOKEN set in the environment's variables to a fine-grained PAT (github_pat_…, 93 chars). Not the proxy-injected placeholder — 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

  1. Correct the GH_TOKEN passthrough wording (and the gh pre-install note).
  2. Add dependabot_alerts: read to the Claude GitHub App's requested repository permissions, so alerts are readable when an installation grants it.
  3. Consider secret_scanning_alerts: read and adding secret-scanning/alerts to the proxy path allowlist, for the same reason. Lower priority — secret scanning results are more sensitive, so gating it differently is reasonable.
  4. 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 workflow scope, blocking .github/workflows/* writes
  • #11078 — proxy strips Authorization, breaking GitHub Packages npm auth
  • #10108 — egress proxy and GitHub subdomains

View original on GitHub ↗