Skip to content

fix(auth): degrade adapter import failures to per-site error rows#1915

Open
Chen17-sq wants to merge 1 commit into
jackwener:mainfrom
Chen17-sq:fix/auth-status-degrade-per-site
Open

fix(auth): degrade adapter import failures to per-site error rows#1915
Chen17-sq wants to merge 1 commit into
jackwener:mainfrom
Chen17-sq:fix/auth-status-degrade-per-site

Conversation

@Chen17-sq

Copy link
Copy Markdown
Contributor

Problem

opencli auth status and opencli auth refresh aggregate results across every registered whoami adapter, concurrently, via mapConcurrent (which await Promise.alls its workers). The whole subsystem is designed to degrade every per-site failure into a result row (rowForError / refreshRowForError) — the "network down" test at auth.test.ts proves this is the intended contract.

But one failure mode was not protected: a lazy/manifest-loaded adapter that throws at import/probe time. Such an adapter rejects its worker promise, which rejects the Promise.all in mapConcurrent, which crashes the entire auth status / auth refresh run — so a single malformed adapter takes down the status of every other site.

Root cause

In src/commands/auth.ts, loadLazyCommand(cmd) performs await import(pathToFileURL(internal._modulePath).href) for lazy adapters (registered with _lazy: true / _modulePath at discovery.ts:139-140). In all three workers this call — plus the dependent command-shaping — sat outside the existing per-site try/catch:

  • runQuickauth.ts:273 (loadLazyCommand + quickCheckCommand) was before the try at line 286.
  • runFullauth.ts:314 (loadLazyCommand + withTimeoutArg) was before the try at line 316.
  • runRefreshauth.ts:355 (loadLazyCommand + refreshCommand + the !refreshCmd unsupported branch) was before the try at line 368.

The catch blocks already map failures to error rows, but they could never see an import-time throw because it happened before the try was entered.

Fix

Move loadLazyCommand() and its dependent command-shaping (quickCheckCommand / withTimeoutArg / refreshCommand), along with the not-implemented (!quickCmd) and unsupported (!refreshCmd) early-returns, inside the existing per-site try in each of the three workers. No signature changes, no new helpers — 3 hunks.

  • An adapter that throws at import now degrades to { status: 'error', logged_in: '', error: <message> } (status) / a status: 'error' row (refresh), exactly like any other adapter failure, and the aggregate resolves with rows for all other sites.
  • runRefresh: the throttle/skip check (lines 343-352) intentionally stays before the try so it is unaffected; the catch continues to record last_attempt_at / last_status: 'error' so refresh state stays consistent and saveAuthRefreshState still runs.

Tests

Added 3 tests to src/commands/auth.test.ts. A helper registers a lazy whoami adapter (_lazy: true + _modulePath pointing at a temp .mjs whose top-level body does throw new Error('boom')), mirroring discovery.ts manifest registration:

  1. collectAuthStatus with one healthy quick-check site + one import-throwing lazy site — asserts it resolves (does not reject) and returns both rows, the bad site as status: 'error' / logged_in: '' / error containing boom, the good site unaffected.
  2. collectAuthRefresh — same mix; the bad site yields a status: 'error' row, the good site is still touched, and the aggregate does not throw.
  3. Asserts the state file is still written (saveAuthRefreshState ran): the good site records last_status: 'touched', the failing site records last_attempt_at / last_status: 'error' with no last_touched_at.

Verification: npx tsc --noEmit clean; npx vitest run src/commands/auth.test.ts -> 13 passed. Confirmed the tests fail on the unpatched source (unhandled boom rejection) and pass with the fix.

Scope

Surgical hardening faithful to the module's existing failure-to-row design; only auth.ts workers + co-located tests. Edge-case (requires a malformed/throwing adapter module), but currently uncovered, so the per-site error contract silently didn't hold for import failures. No open issue/PR touches this file (only #1879/#1881, both merged).

loadLazyCommand() awaits import() of lazy/manifest adapters but ran
outside the per-site try/catch in runQuick/runFull/runRefresh. An
adapter that throws at import time rejected its worker promise, which
rejected the Promise.all in mapConcurrent and crashed the entire
auth status / auth refresh aggregate.

Move loadLazyCommand() and the dependent command-shaping (quickCheckCommand
/ withTimeoutArg / refreshCommand, plus the not-implemented/unsupported
early-returns) inside the existing per-site try so import/probe failures
degrade to an error row via rowForError / refreshRowForError, matching the
module's existing failure-to-row design. The runRefresh throttle/skip check
stays before the try; the catch path still records last_attempt_at /
last_status so state stays consistent.
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.

1 participant