Skip to content

mcpdo: a sign-in helper failure without a TTY loses its exit-code classification (fetch_refused reported as auth_required) #2962

Closed

Description

@cliffhall

Found in the v2.15.0 release smoke (#2960). It is a follow-up to #2842 / PR #2940, which classified CLI failures by exit code but only covered the front-end path.

Problem

When mcpdo connect runs without a TTY, OAuth sign-in runs in the detached sign-in helper. Any failure the helper reports before it produces the authorization URL is turned into auth_required / exit 3 by fail() in clients/mcpdo/src/connection/auth-helper.ts. That happens even when the front-end path classifies the same failure more specifically.

The case observed in the smoke was a fetch-guard refusal. The same server gives different results depending on the path:

Path Envelope Exit
one-shot CLI {"error":{"code":"fetch_refused","message":"Refusing to fetch from a link-local or metadata address (169.254.169.254)","url":"http://169.254.169.254/.well-known/oauth-authorization-server"}} 12
mcpdo connect with a TTY fetch_refused 12
mcpdo connect without a TTY {"error":{"code":"auth_required","message":"Sign-in helper failed: Refusing to fetch from a link-local or metadata address (169.254.169.254)"}} 3

The url field is lost as well.

Reproduce

  1. Run an OAuth composable test server with "mode":"protected-resource","authorizationServers":["http://169.254.169.254"] on a local port.
  2. HOME=<fresh> mcpdo --format json connect http://127.0.0.1:<port>/mcp </dev/null; echo $?
  3. The output is auth_required / 3. The expected output is fetch_refused / 12, the same as the TTY path.

Who hits it

The path is common, but the trigger is narrow. Running without a TTY is how agents drive mcpdo (see skills/mcpdo). The issue only fires on a failure before the authorization URL exists:

  • a fetch-guard refusal
  • a discovery failure
  • a client-registration error

A user declining consent happens after the URL exists, so it is not affected. The message still names the cause, and exit 3 still reads as an auth problem, so only a caller that distinguishes 12 from 3 misroutes.

Expected

The helper relays enough of the original error, such as a code and a url, for the parent to raise the same classified CliExitCodeError the TTY path does. auth_required should be the fallback only for failures that have no classification.

Activity

  1. added this to the v2.16.0 milestone on Nov 11, 2026
  2. added
    bugSomething isn't working
    v2Issues and PRs for v2
    on Nov 11, 2026
  3. cliffhall commented on Nov 11, 2026

    @cliffhall
    MemberAuthor

    Triage: Priority Medium (total 6)

    • Severity 2: minor friction. Exit 3 still reads as an auth problem and the message names the cause; only callers that tell 12 apart from 3 misroute.
    • Urgency 2: wanted eventually; nothing is blocked on it.
    • Bonuses: +1 bug label, +1 milestone (v2.16.0, filed by a maintainer).

    Board: #28, Status Todo.

  4. self-assigned this
    on Nov 12, 2026
  5. added 2 commits that reference this issue on Nov 12, 2026
  6. cliffhall commented on Nov 12, 2026

    @cliffhall
    MemberAuthor

    Fixed by #2971 (merged as a53ce55).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingv2Issues and PRs for v2

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions