Skip to content

Device-login polling logic is duplicated and the deviceLogin() fallback is dead/divergent #18

@eliottreich

Description

@eliottreich

Problem

The device-auth flow exists twice in src/index.ts: once in the standalone deviceLogin() function (lines 197-330) and again, near-identically, inline in the taskbounty_login handler (lines 628-744). The two copies have already diverged, and the only path that reaches deviceLogin() makes it redo work it cannot reach correctly.

Evidence

taskbounty_login handler, lines 628-643:

let start: DeviceStart | null = null;
try {
  const res = await fetch(`${SITE_ORIGIN}/api/mcp/device/start`, { ... });
  if (res.ok) start = (await res.json()) as DeviceStart;
} catch {
  start = null;
}
if (!start) {
  return await deviceLogin(clientName);
}

deviceLogin() (line 197+) then immediately performs the same /api/mcp/device/start POST again. So the only time deviceLogin() runs is when the first start call failed, and its first action is to retry the exact call that just failed, with no backoff. The polling loops in the two copies are otherwise duplicated logic (authorization_pending, slow_down, expired_token, access_denied, deadline handling), and they already differ: the inline copy prepends an instruction string to every result, the function copy does not. Divergence in auth-critical code is the bug.

Why it matters

This is the credential-bootstrap path for every creator tool. Two copies of an auth state machine means a fix or behavior change has to be made in both places or the two flows silently behave differently (they already do, re: the instruction text). It is also confusing for any agent attempting to fix or extend login.

Acceptance criteria

  • The device-auth start + poll logic exists in exactly one place; taskbounty_login and the fallback path both call it.
  • The single implementation surfaces the approval instruction consistently regardless of which entry point triggered it.
  • A regression test exercises the poll state machine (e.g. mocked authorization_pending then success, and the expired_token path) against the single implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions