Repository navigation
Regression (1.0.88 → 1.0.89): MCP OAuth authorization page opens on every ACP session instead of the challenge being resolved silently #4994
Description
Describe the bug
An OAuth-protected HTTP MCP server that has a valid, unexpired access token in
~/.copilot/mcp-oauth-config is answered with HTTP 401 on every new --acp
session, and the CLI responds by opening the OAuth authorization page again.
Because the browser SSO session is already established, the flow self-approves
and the tab closes — so the user sees an unrequested sign-in page flash open on
every single session start, with no way to suppress it.
This is a regression: 1.0.88 connects the same server, with the same token,
and resolves the identical OAuth challenge silently — no page, no interaction.
Critically, the stored token has no refresh token (the tokens file contains
only accessToken, expiresAt, scope), so there is no silent renewal path —
the CLI appears to fall straight through to the interactive flow rather than
using the access token it already holds.
Affected version
- Last known good:
1.0.88 - Broken:
1.0.89
A single version bump. Both were tested on the same machine, minutes apart, against
the same MCP server and the same on-disk token.
| Installed | Size | SHA-256 (first 16) | --no-auto-update --version |
Behaviour |
|---|---|---|---|---|
| Aug 14 | 152.1 MB | 4F590F1F60F3F2BD |
1.0.80 | works — challenge resolved silently |
| Sep 23 | 144.2 MB | E2160809894BA950 |
1.0.88 | works — challenge resolved silently |
| Sep 29 | 144.8 MB | 155778DD92A2171A |
1.0.89 | broken — page opens every session |
Secondary reporting bug worth fixing on its own: plain copilot --version
reported 1.0.89 for all three binaries, including the 1.0.80 one. Only
copilot --no-auto-update --version reveals the real built-in versions above.
That made this regression very hard to characterise — it looked like one version
behaving two different ways. If --version is reporting a cached/candidate
package rather than the running build, it should say so.
- OS: Windows 11 (10.0.26200)
- Mode:
copilot --acp(ACP server), 4 ×--plugin-dir - MCP protocol negotiated:
2025-11-25
Steps to reproduce the behavior
- Configure an OAuth-protected HTTP MCP server in
~/.copilot/mcp-config.json:
https://mcp.internal.example.com/servers/REDACTED-MCP/mcp - Complete its OAuth flow once. Confirm
~/.copilot/mcp-oauth-confignow holds a
registration with"isStatic": falseand a paired*.tokens.jsonwhose keys are
exactlyaccessToken,expiresAt,scope— i.e. norefreshToken, and
expiresAtcomfortably in the future (mine was ~3 days out). - Start
copilot --acpand driveinitialize→authenticate→session/new,
passing that server inmcpServers. - Observe: the authorization page opens even though the token is still valid.
- Repeat step 3. It opens again, every time.
Expected behavior
With a valid unexpired access token on disk, session/new should connect the MCP
server using that token and open nothing. If the server genuinely rejects the
token with 401, the CLI should surface an actionable error rather than silently
launching a browser authorization flow on every session — that behaviour is
unusable for headless or unattended ACP clients.
Actual behavior — CLI log
From ~/.copilot/logs/process-*.log on the Sep 29 build (hostnames redacted):
[ERROR] [rust:rmcp::transport::worker] worker quit with fatal: Transport channel closed,
when Client(OAuthChallenge {
www_authenticate_header: "******\"https://mcp.internal.example.com/.well-known/oauth-protected-resource/servers/REDACTED-MCP/mcp\"",
response: McpOAuthHttpResponse { status_code: 401, header_count: 6, has_body: true } })
[INFO] [rust:acp::mcp_oauth] opened the MCP OAuth authorization page {"server_name":"REDACTED-MCP"}
[WARNING] [rust:copilot_runtime::session::mcp::agent_host] HTTP 401 challenge
(WWW-Authenticate: ******"https://mcp.internal.example.com/.well-known/oauth-protected-resource/servers/REDACTED-MCP/mcp")
{"server":"REDACTED-MCP"}
Possibly the same root cause — the new discovery call is rejected for lack of a
bearer token, then falls back:
[WARNING] [rust:mcp::client] server/discover failed; retrying with legacy initialize
{"error":"unexpected server response: HTTP 400 Bad Request: {\"error\":\"Invalid or missing bearer token\"}"}
A/B isolating the build
Same machine, same 3-minute window, same on-disk token (issued earlier that day,
expiresAt 3 days out), identical client invocation and identical MCP server list.
Only the copilot.exe binary was swapped between runs:
| Run | Version | opened the MCP OAuth authorization page |
401 challenge |
|---|---|---|---|
| 15:29 | 1.0.80 (4F590F1F…) |
0 | 0 |
| 15:32 | 1.0.89 (155778DD…) |
1 | 2 |
| 20:33 | 1.0.88 (E2160809…) |
0 | 0 |
Because the token was unchanged and still valid across all three runs, token expiry
is ruled out as the cause; the binary is the only variable. 1.0.88 passing puts the
regression squarely in the 1.0.88 → 1.0.89 change.
On 1.0.88 and 1.0.80 the server issues the same OAuth challenge — the log still shows
worker quit with fatal: Transport channel closed, when Client(OAuthChallenge {…})
— but it is then resolved without any user interaction:
[ERROR] Successfully authenticated with co-design
[INFO] [rust:rmcp::service] Service initialized as client { … }
[ERROR] Handling tools refresh for co-design
So the regression is not "the server started returning 401". It is that 1.0.89 no
longer completes that challenge silently and instead falls back to opening the
interactive authorization page. (Those two lines are also logged at ERROR level
on success in 1.0.80, which looks like a separate log-level mistake.)
Impact
Every new ACP session pops a browser window the operator did not ask for. For any
non-interactive ACP client (our case: a local web UI and scheduled agent runs)
this is disruptive, and on a headless host the flow has nobody to approve it.
Activity
[-]Regression: MCP OAuth authorization page reopens on every ACP session for a server with a valid (non-refreshable) access token[/-][+]Regression (1.0.88 → 1.0.89): MCP OAuth authorization page opens on every ACP session instead of the challenge being resolved silently[/+]grahamannett commentedon Oct 16, 2026
Running into this as well!
pWoLiAn commentedon Oct 19, 2026
Still reproduces on 1.0.98 (macOS arm64,
copilot --acplaunched by an ACP client), and from what I can see the problem is worse than the original report. The token is never persisted at all.Setup: project
.mcp.json→{"type":"http","url":"https://mcp.atlassian.com/v2/mcp"}Observed
copilot --acpprocess. On startup it getsHTTP 401 challenge→[acp::mcp_oauth] opened the MCP OAuth authorization page.Service initialized as clientabout 12s after the page opened).~/.copilot/mcp-oauth-config/does not exist (HOME is the normal user home, noCOPILOT_HOME). No MCP-related macOS keychain item exists, and no "token saved/persisted" log line appears. The token seems to live only in that process's memory.MCP OAuth login did not complete … timed out after 60000ms.MCP OAuth authorization failed; reporting the server as needing auth, with no browser opened.Expected / requests
andriy-ah commentedon Oct 27, 2026
Super annoying, tried to use Copilot in T3 Code and it constantly opens new browser tabs to authenticate. Also reproducible with OpenCode. The issue is 1 month old and seems quite critical, how is it not being worked on? Really disappointed by Copilot team, sent a number of issues already in discussions and don't see any replies from the team. Makes me question whether we need to switch to something else from Copilot.
lukecharman commentedon Oct 27, 2026
Still reproduces on 1.0.104 (macOS, Homebrew). Repro without a host app: a bare stdio ACP client running
copilot --acp --yologets "MCP server 'Atlassian' requires authentication" on an Atlassian tool call. The same binary withcopilot -puses the stored keychain token (servicecopilot-mcp-oauth) and works. Authorising during an ACP session works only for that session. The keychain entry's modified time doesn't change, and the prompt returns on restart. +1 to lazy auth (don't open a browser at session start).summer4096 commentedon Oct 29, 2026
Here is a self-contained repro that needs no real MCP server, no GitHub sign-in and no browser interaction. It exits 0 when the bug is fixed and 1 when it is present, so it can double as a regression check.
It starts an OAuth-protected mock MCP server on 127.0.0.1 (protected-resource metadata, authorization-server metadata, dynamic client registration, PKCE code flow) and replaces
open/xdg-openwith shims that record each authorization page and approve it, like a browser with an existing SSO session. Each session is a separatecopilot --acpprocess (initialize, thensession/newwith the server inmcpServers), using a temporaryCOPILOT_HOME, auto-update off and a placeholder BYOK provider instead of GitHub sign-in. Session 1 may sign in; later sessions must reuse the stored token.Results on macOS arm64, 3 sessions each:
The same split holds with
--refresh-token, where the mock also issues a refresh token.What the mock and the CLI show on 1.0.89 and later:
MCP OAuth token exchange completed; saving tokensandMCP OAuth tokens saved, but nothing is written to the macOS keychain (servicecopilot-mcp-oauth) or to$COPILOT_HOME/mcp-oauth-config. On 1.0.88 the same run writes a registration and a token to the keychain, and later sessions connect with that token without opening any page.HTTP 401 challenge/opened the MCP OAuth authorization pagepairs within about 15 ms, which suggests two connection attempts each starting their own flow.On macOS the script deletes the keychain entries created during the run. Options:
--sessions N,--refresh-token,--keep(keeps the temporaryCOPILOT_HOMEwith the CLI logs) and-v.copilot-cli-4994-repro.py