anthropics / anthropics/claude-code
Cloud sessions: GH_TOKEN set in environment variables is ignored for api.github.com; Dependabot alerts unreachable
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
## 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:
```console
$ 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.
```console
$ 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](https://code.claude.com/docs/en/cloud-environments) 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:
```console
$ 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](https://github.com/orgs/community/discussions/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
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the cloud-environments documentation section and the GitHub proxy behavior described in the reproduction commands. Verify the REST endpoints and response signatures for api.github.com, Dependabot alerts, and secret-scanning alerts. Done means the token and gh wording is accurate and the requested App permissions and proxy allowlist changes have clear, reviewed decisions.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- github
- Domain
- api, authentication, cloud, documentation, security
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100