Skip to content

mcpdo: a stale OAuth-flow lock steal can delete a fresh lock and allow a second sign-in helper #2643

Description

@cliffhall

Problem

Raised by Copilot on the v2.10.0 milestone merge (#2637, round 3), and traced to tryReserveAuthFlow in clients/mcpdo/src/connection/auth-helper.ts.

When the per-server .lock is stale (a crashed reserver), two stealers can interleave:

  1. A renames the stale lock to …claim-A, finds it stale, removes it, and create()s a fresh lock. A now holds the flow.
  2. B, which also saw the old lock as stale, now renames A's fresh lock to …claim-B. It correctly sees the lock is fresh (claimedFresh) and backs off, but it does so with fs.rmSync(claimPath), which deletes A's lock.
  3. A third connect finds no lock, wins create(), and spawns a second sign-in helper. The two helpers contend for the OAuth callback port.

The comment at the claimedFresh branch calls the window "un-injectable", but the back-off branch should not destroy the lock it just identified as live.

Expected

On claimedFresh, put the lock back instead of deleting it: for example fs.linkSync(claimPath, lockPath), which fails if a new lock exists, and then remove the claim. That way only a lock confirmed stale is ever discarded. Add a test that injects the interleaving through the fs seams.

Effect

Needs a crashed reserver plus three concurrent non-TTY connects to the same server within the same instant. The worst case is two helpers contending for the callback port and one failing. Nothing leaks.

Priority

Low (rubric total 5): Severity 2, Urgency 1, +1 bug, +1 milestoned.

Activity

  1. added this to the v2.11.0 milestone on Oct 7, 2026
  2. added
    bugSomething isn't working
    v2Issues and PRs for v2
    on Oct 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    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