Skip to content

Serialize device auth to reuse concurrent logins#197

Merged
ericmj merged 1 commit into
hexpm:mainfrom
maennchen:jm/linear-device-auth
Jun 16, 2026
Merged

Serialize device auth to reuse concurrent logins#197
ericmj merged 1 commit into
hexpm:mainfrom
maennchen:jm/linear-device-auth

Conversation

@maennchen

Copy link
Copy Markdown
Member

Problem

device_auth was the only auth-resolution path in hex_cli_auth not wrapped in global:trans. Concurrent callers that all needed to authenticate (e.g. parallel commands run with no credentials) each kicked off their own device auth flow — prompting the user and persisting a token multiple times instead of sharing a single login.

Fix

  • Wrap maybe_authenticate_and_retry in a single global lock. The device auth token is always persisted to the global OAuth scope, so the lock is intentionally not keyed by repo.
  • Inside the lock, re-resolve auth first and reuse a token that differs from the one the caller arrived with — a caller that was waiting on the lock picks up the login a prior winner just persisted instead of prompting again.
  • The "differs from current" check covers both entry reasons:
    • no_credentials — no key in config, so any resolved token differs → reuse.
    • token_refresh_failed — the rejected key is in config, so the winner re-runs device auth while a later waiter reuses the freshly persisted token.
    • It also avoids an infinite retry loop when the server keeps rejecting a token that still looks valid by expiry.

Test

Adds a regression test that drives concurrent callers through a barrier and blocks the winner inside should_authenticate, asserting exactly one caller is prompted and the persisted login is reused by the rest. It fails against the unfixed code (multiple concurrent prompts) and passes with the fix. Full suite: 33/33.

device_auth was the only auth-resolution path in hex_cli_auth not wrapped
in global:trans. Concurrent callers that all needed to authenticate (e.g.
parallel commands with no credentials) each kicked off their own device
auth flow, prompting the user and persisting a token multiple times instead
of sharing a single login.

Wrap maybe_authenticate_and_retry in a single global lock (the device auth
token is always persisted to the global OAuth scope, so the lock is not
keyed by repo). Inside the lock, re-resolve auth first and reuse a token
that differs from the one we arrived with: a waiting caller picks up the
login a prior winner just persisted instead of prompting again. The
"differs from current" check covers both entry reasons — no_credentials
(no key in config) and token_refresh_failed (the rejected key in config) —
and avoids an infinite retry loop when the server keeps rejecting a token
that still looks valid by expiry.

Add a regression test that drives concurrent callers through a barrier and
blocks the winner inside should_authenticate, asserting exactly one caller
is prompted and the persisted login is reused by the rest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@maennchen maennchen force-pushed the jm/linear-device-auth branch from 30ac561 to cd1d29f Compare June 16, 2026 11:31
@ericmj

ericmj commented Jun 16, 2026

Copy link
Copy Markdown
Member

@maennchen Let me know if this is ready to merge

@maennchen

Copy link
Copy Markdown
Member Author

@ericmj Ready. But I'll open more for other stuff surfacing over in hex later.

@ericmj ericmj merged commit cd3d425 into hexpm:main Jun 16, 2026
10 checks passed
@maennchen maennchen deleted the jm/linear-device-auth branch June 16, 2026 16:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants