Repository navigation
Description
Problem
Independent xurl mcp processes on the same machine, using the same app/user and shared ~/.xurl/auth.yml, can attempt to redeem the same stale OAuth2 refresh token. Each process loads its own token-store snapshot. Refresh is serialized within one bridge, but not across processes, and the store is not reloaded before refreshing.
Once one process rotates the grant, another process can use the old refresh token. At startup, mcpBridge.bootstrap() treats any token-resolution error as a reason to open interactive browser login and does not preserve the original error in its diagnostic.
This is distinct from #22: it requires neither separate machines nor a second interactive authorization. The clients intentionally share one persisted login.
Environment
- Official xurl v1.3.4, commit
18dcb447f090667171ff23666a05d6d387e2aa73 - macOS arm64
- Multiple stdio MCP clients, including Codex and Claude Code, launching the same
xurl mcp https://api.x.com/mcpbridge
Deterministic reproduction
I reproduced the refresh defect using four independent test processes and a local HTTP token endpoint. No real X credentials or calls are needed:
- Persist one expired access token and a refresh token in a temporary shared store.
- Start four independent Auth instances in separate processes. Make all four load the store before proceeding.
- Release a barrier so they request a valid token together.
- Have the fake provider accept the old refresh token once, return a replacement access/refresh pair, and reject reuse with
invalid_grant.
Actual on stock v1.3.4: at least one child process fails because it submits the stale grant.
Expected: one process refreshes and persists the replacement; other processes reload it and reuse the new access token. The endpoint receives exactly one refresh request.
A local prototype with a cross-process file lock, reload inside the lock, and atomic store replacement passes that test. It also handles a forced refresh when another process has already replaced the token.
Scope of the evidence
This establishes the refresh-coordination defect. Repeated browser login pages prompted the investigation, but I cannot prove that this race accounts for every observed popup. Access tokens expiring after two hours is expected; a stored refresh token should permit silent renewal.
The proposed fix and regression test are in #103. No credentials, account-specific configuration, or private logs are included.
Activity
maartengoet commentedon Oct 17, 2026
Same failure mode here on macOS arm64 with xurl 1.3.4 via
npx: Codex (ChatGPT desktop) keeps onexurl mcpbridge per thread alive for days, plus one bridge per Claude Code session, all sharing one~/.xurl/auth.yml. New bridges hit the bootstrap fallback (no valid OAuth2 token; opening the browser to sign in) while the already-running ones silently fail their refresh.Reviewed and tested #103:
go vet,go test -race(incl. the 4-process regression test), Windows/Linux cross-builds and gofmt all pass, and a build of that branch now runs as the bridge here without the browser prompt. Details in #103 (review)