Repository navigation
Description
Summary
mcp-publisher login github fails with incorrect_device_code at the authorization step. Observed three times across two days and two publisher versions (1.7.9 twice on 2026-08-17, 1.8.1 once on 2026-08-18), same host. Polling while the code is still pending is healthy; the error appears only in runs where the user proceeds to authorize in the browser, well inside the 15-minute expiry window.
Logging in with github...
To authenticate, please:
1. Go to: https://github.com/login/device
2. Enter code: XXXX-XXXX
3. Authorize this application
Waiting for authorization...
Error: login failed: error polling for token: token request failed: incorrect_device_code
Per RFC 8628 and GitHub's device-flow docs, a pending poll should return authorization_pending, and incorrect_device_code means the polled device_code does not match one issued for that client_id. Getting it after a successful code issuance, from the same process that requested the code, is the puzzle.
Environment
- publisher 1.7.9 (commit 35b2071, built 2026-05-12) and 1.8.1 (commit f52dc85, built 2026-08-06, checksum-verified from the release), linux amd64
- registry: https://registry.modelcontextprotocol.io (health endpoint serves
github_client_id: Iv23liUydBbI7Z2Q9bOZ) - no proxy env vars, DNS resolves github.com normally
What we ruled out
- The binary version. 1.7.9 and 1.8.1 fail identically.
- The pending-poll path. Ran
mcp-publisher login github(1.8.1) and let it poll for 40 seconds without authorizing anything: no error, it polls quietly. So the loop, the payload it sends, and the network path are all fine while the code is pending. - The wire and the payload shape. Reproduced the publisher's exact requests manually from the same host:
POST https://github.com/login/device/codewith{"client_id":"Iv23liUydBbI7Z2Q9bOZ","scope":"read:org read:user"}(JSON, same headers) issues a device code, and a single poll ofPOST https://github.com/login/oauth/access_tokenwith the publisher's exact payload returnsauthorization_pendingas expected. - The account/namespace. The
MCP_GITHUB_TOKENprovided-token path logs in and publishes fine with the same GitHub account (that is how we shipped in the end).
What we could not capture
Browser-side behavior at the authorize step. The failures correlate with the user completing (or attempting) authorization at github.com/login/device, but we did not record what GitHub displayed there. One of the three failures arrived within seconds of a rerun.
Related
#1289 is the same poll loop failing terminally on slow_down. Two observations about pollForToken in cmd/publisher/auth/github-at.go compound whatever the underlying GitHub-side condition is:
- every non-
authorization_pendingerror is treated as terminal, so a transient or ambiguous response kills the whole login irrecoverably; - the loop hardcodes
interval = 5and ignores theintervalthe device-code response actually returns.
Ask
- Is the device flow for the
Iv23liUydBbI7Z2Q9bOZGitHub App known-healthy right now? Has anyone else reportedincorrect_device_codeafter authorizing? - Independent of the root cause, could the poll loop distinguish terminal from transient errors per RFC 8628 (which would also close mcp-publisher: device-flow login treats GitHub
slow_downas fatal instead of backing off #1289)? A login that dies on the last hop after the user has authorized is the most expensive place to fail.
Activity
JosephDoUrden commentedon Aug 27, 2026
Looked into this. I can reproduce the exact failure text in a test harness, and I've ruled out the obvious client side causes so far, the client id is fetched once per login and stays stable, and the poll payload is marshalled once so it can't drift between polls. Writing up the full analysis, will post it here.
JosephDoUrden commentedon Aug 28, 2026
Full analysis, as promised in the claim comment. Short version: I could not make any client-side cause survive testing; the evidence points at a transient on GitHub's side at the claim step, for which there is direct precedent. What the client does control is that it turns one transient bad answer into a dead login, and that part is fixable regardless of root cause.
Reproducing the failure path
The exact symptom string —
login failed: error polling for token: token request failed: incorrect_device_code— reproduces byte-for-byte in an httptest harness the moment the token endpoint answersincorrect_device_codeonce. Every non-authorization_pendingerror inpollForTokenis terminal on first sight, so a single anomalous response at the very moment the user authorises kills the whole login. That matches your observation that pending-polling is healthy and the failure correlates with the authorise step: pending is the only error the loop tolerates.Client-side hypotheses, falsified
Iv23liUydBbI7Z2Q9bOZon every probe today.git diff 35b20719 f52dc852 -- cmd/publisher/auth/is empty. The auth code is byte-identical between 1.7.9 and 1.8.1, which is why they fail identically — independently confirms your ruling-out of the binary.Precedent
cli/cli#9302 is the same shape — device-flow logins intermittently failing at the claim step with device-code errors — and was closed by a cli maintainer with: "I believe this was a race condition in the platform which is now fixed." (Jul 2024.) So GitHub's device flow has produced exactly this class of transient before, platform-side. Whether what you hit is a recurrence or a sibling race, it is consistent with everything you captured, including the failure arriving within seconds of a rerun.
What the client should do regardless
I have the loop hardened on top of #1290's branch (it touches the same code, and #1290 should land first — its four tests pass unmodified under these changes, credit to its author):
incorrect_device_codea couple of times before declaring the login dead — a genuinely invalid code fails identically a few seconds later, a transient recovers;error_description/error_uri, so the next report of this is not opaque;interval/expires_inthe device-code response actually returns (RFC 8628 §3.2) instead of the hardcoded 5s/900s;77+/4− plus seven tests, including one that pins the exact symptom string from this issue. Happy to open it as a PR once #1290 lands, or fold whichever parts are wanted into a different shape.
Open questions for maintainers: (1) any other reports of
incorrect_device_codeafter authorise against the registry's app? (2) is on-top-of-#1290 the right shape, or should the resilience changes go separately? (3) any objection to grace-retryingincorrect_device_codespecifically? (4) worth takingerror_descriptionsurfacing on its own even if the rest waits?john-broadway commentedon Sep 1, 2026
Retested today: same 1.8.1 binary (commit f52dc85, the checksum-verified release build that failed on 2026-08-18), same host, full device flow. It succeeded end to end, including the authorize step where all three original failures fired:
That lands exactly where @JosephDoUrden's analysis pointed: no client-side cause survives testing, and the precedent says a transient on GitHub's side at the claim step. Thank you for that work, it is a thorough piece of elimination and it made this retest the only step left.
Closing as resolved-by-transient. The two
pollForTokenobservations stand on their own regardless: treating every non-authorization_pendingresponse as terminal is what turned a one-poll blip into a dead login, and the hardcoded 5s interval ignores what the device-code response returns. #1289 tracks theslow_downhalf of that. A GitHub hiccup should cost one poll, not the whole flow.fix(publisher): tolerate transient device-flow failures during login …
feat(registry): publish to the MCP Registry from CI, with the version…
release: 0.11.1 — listed on the official MCP Registry