You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
github-copilot (Enterprise): first prompt after Location boot sends the Enterprise token to api.githubcopilot.com #62959
Follow-up to #55490, as requested when it was closed.
With a GitHub Copilot Enterprise (ghe.com) credential, the first prompt after a Location boots (e.g. after idle eviction) is sent to the public api.githubcopilot.com instead of the account's copilot-api.<enterprise>.ghe.com endpoint. It carries the Enterprise OAuth token in the Authorization header. A retry a moment later goes to the Enterprise endpoint and succeeds.
This is the race described in this comment on #55490 (loaded.baseURL in github-copilot.ts is only set after the initial /models fetch). The comment notes that the actual destination of the failed request was not captured. I can confirm it: opencode2 runs inside a sandbox whose egress proxy only injects the token for the Enterprise hosts, and its log records the blocked request:
2026-11-09T20:55:12.923Z WARN secret violation: placeholder detected where substitution or passthrough is not permitted
host=api.githubcopilot.com method=POST path=/responses location=header
The opencode log for the same moment:
20:55:10.908 config load diagnostics (Location boot after ~3.5 h idle)
20:55:12.375 event.type=provider.updated
20:55:12.390 event.type=provider.updated
20:55:13.102 ERROR Failed to drain Session cause="AI.Error: Request blocked by secret policy."
It happened twice that day, both times on the first prompt after a long idle period (17:11 and 20:55 UTC). Outside the sandbox (same Enterprise account, plain opencode2 on the host), the first prompt after idle fails with Provider request failed with HTTP 401 and the retry succeeds, which matches the token being sent to the public endpoint.
Besides the failed first prompt, this sends an Enterprise credential to a host it is not meant for. Data-residency accounts in particular would expect their token to stay on their own endpoint.
Expected: requests for an Enterprise credential never use the default host. The base URL derived from the credential's enterpriseUrl metadata is known before the model fetch, so it could be applied immediately instead of after /models completes (or the first request could wait for the initial sync, as proposed in #38204).
Plugins
None
OpenCode version
2.2.6 (sandbox), 2.2.5 (host)
Steps to reproduce
Log in to the github-copilot provider with a GitHub Enterprise (ghe.com) account.
Leave a session idle until its Location is evicted.
Send a prompt: it fails (HTTP 401, or blocked by an egress proxy that only allows the Enterprise host).
Send it again: it succeeds.
Operating System
Linux (Fedora 44, x86_64); sandbox guest is Debian
Thanks for the follow-up and the proxy log, they pinned this down.
I reproduced it on 2.2.6. The script below:
imports a github-copilot OAuth credential with enterpriseUrl and apiEndpoint metadata;
points the account endpoint at a local mock that answers /models after a few seconds;
routes everything else through a logging proxy that refuses it;
sends a prompt with opencode run --standalone on a fresh Location.
While /models is still loading, the prompt's requests go to api.githubcopilot.com:443, which the proxy refuses. Once the model list has loaded, requests go to the account's own endpoint. It still happens on the latest dev (1830078).
The cause looks like the one described on #55490. The plugin's initial load runs in the background, and until it finishes, Copilot models fall back to the default api.githubcopilot.com base URL. The credential's own endpoint is already known at that point but isn't used.
#!/usr/bin/env bash# Reproduces anomalyco/opencode#62959: with a GitHub Copilot Enterprise credential, the first prompt after a# cold start (fresh Location) goes to the public api.githubcopilot.com with the Enterprise token.## Setup:# - an HTTPS proxy (HTTPS_PROXY) logs every CONNECT host and refuses it (stands in for the egress proxy),# - a mock Copilot API on 127.0.0.1 is the account's endpoint (credential metadata.apiEndpoint), and it answers# /models after 8 s, standing in for a slow initial model sync,# - a github-copilot OAuth credential with metadata { enterpriseUrl: "acme.ghe.com", apiEndpoint: <mock> }.# Then `opencode run --standalone` boots a fresh Location and sends a prompt immediately.## Usage: bash repro.sh (uses `opencode` from PATH)# OC_CWD=<repo>/packages/cli OC="bun --conditions=browser <repo>/packages/cli/src/index.ts" bash repro.sh (from source)set -u
OC=${OC:-opencode}
W=$(mktemp -d /tmp/opencode/repro.XXXX)
mkdir -p "$W/home""$W/project"export HOME="$W/home" XDG_CONFIG_HOME="$W/home/.config" XDG_DATA_HOME="$W/home/.local/share" \
XDG_STATE_HOME="$W/home/.local/state" XDG_CACHE_HOME="$W/home/.cache"
cat >"$W/servers.ts"<<'EOF'import net from "node:net"const log = (s: string) => console.log(new Date().toISOString(), s)// HTTPS proxy: log the CONNECT target and the Authorization of plain requests, refuse everything.net.createServer((sock) => { sock.once("data", (buf) => { const line = buf.toString().split("\r\n")[0] const host = line.split(" ")[1] ?? "" // Let the models.dev catalog through so the github-copilot models exist; refuse every other host. if (host.startsWith("models.opencode.ai:") || host.startsWith("models.dev:")) { const [h, p] = host.split(":") const up = net.connect(Number(p), h, () => { sock.write("HTTP/1.1 200 Connection Established\r\n\r\n") up.pipe(sock) sock.pipe(up) }) up.on("error", () => sock.destroy()) return } log(`PROXY BLOCKED ${line}`) sock.end("HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n") })}).listen(18080)// Mock enterprise Copilot API (the account's endpoint).Bun.serve({ port: 18081, async fetch(req) { const url = new URL(req.url) log(`MOCK ${req.method} ${url.pathname} auth=${req.headers.get("authorization")}`) if (url.pathname.endsWith("/models")) { await Bun.sleep(8000) return Response.json({ data: [] }) } return new Response("mock enterprise endpoint", { status: 500 }) },})log("servers up")EOF
bun "$W/servers.ts">"$W/servers.log"2>&1&
SRV=$!
sleep 1
cat >"$W/cred.json"<<'EOF'[{"id":"cred_enterprise_repro","integrationID":"github-copilot","label":"acme","active":true, "value":{"type":"oauth","methodID":"device","refresh":"gho_ENTERPRISE_TOKEN","access":"gho_ENTERPRISE_TOKEN","expires":0, "metadata":{"enterpriseUrl":"acme.ghe.com","apiEndpoint":"http://127.0.0.1:18081"}}}]EOFexport HTTPS_PROXY=http://127.0.0.1:18080 https_proxy=http://127.0.0.1:18080 NO_PROXY=127.0.0.1,localhost no_proxy=127.0.0.1,localhost
cd"${OC_CWD:-$W/project}"# from source, set OC_CWD=/workspace/repo/packages/cli so its bunfig.toml applies$OC auth import --standalone "$W/cred.json"echo"--- servers log before the prompt"; cat "$W/servers.log";:>"$W/servers.log"echo"--- opencode run (fresh Location, prompt sent immediately)"
timeout 60 $OC run --standalone -m github-copilot/claude-opus-5.5 "hello"2>&1| tail -15
sleep 1
echo"--- servers log during the run"
cat "$W/servers.log"kill$SRV
Activity
opencodejr commentedon Nov 9, 2026
Thanks for the follow-up and the proxy log, they pinned this down.
I reproduced it on 2.2.6. The script below:
github-copilotOAuth credential withenterpriseUrlandapiEndpointmetadata;/modelsafter a few seconds;opencode run --standaloneon a fresh Location.While
/modelsis still loading, the prompt's requests go toapi.githubcopilot.com:443, which the proxy refuses. Once the model list has loaded, requests go to the account's own endpoint. It still happens on the latestdev(1830078).The cause looks like the one described on #55490. The plugin's initial load runs in the background, and until it finishes, Copilot models fall back to the default
api.githubcopilot.combase URL. The credential's own endpoint is already known at that point but isn't used.Related:
Reproduction script (repro.sh)
Run it:
bash repro.sh