Skip to content

πŸ› fix(browse): require stored auth before entering interactive browser - #2

Merged
kud merged 1 commit into
mainfrom
fix/browse-auth-check
Jul 31, 2026
Merged

kud merged 1 commit into
mainfrom
fix/browse-auth-check

Conversation

@kud

@kud kud commented Jul 31, 2026

Copy link
Copy Markdown
Owner

The bug

$ npm run dev -- browse

> @kud/pcloud-cli@0.7.0 dev
> tsx src/cli.ts browse

$

Nothing. No error, no browser, no clue.

Why

The check was never missing. @kud/pcloud-ink's buildAPI() does exactly the
right thing:

const api = resolveStoredAuth();
if (!api) {
  console.error("Not authenticated. Run `pcloud login` first.");
  process.exit(1);
}

The problem is when it runs. startBrowse mounts with
{ alternateScreen: true }, so by the time that console.error fires the
terminal has already switched to the alternate buffer. process.exit(1) restores
the primary buffer β€” and takes the message with it.

A correct check, a correct message, and a completely silent command.

The fix

Gate in cli.ts before the dynamic import, while the primary buffer is still on
screen:

requireStoredAuth();
const { startBrowse } = await import("./browse.js");

browse now fails like every other command in the CLI:

$ pcloud browse

❌ Not authenticated!

It looks like you haven't set up pCloud CLI yet.

Please run this command first:

  pcloud login

The in-component check in @kud/pcloud-ink stays as-is β€” cockpit mounts that
component directly, so it still needs its own guard.

Notes for review

resolveStoredAuth, not resolveAuth. The latter falls through to an
interactive OAuth browser round-trip when PCLOUD_CLIENT_ID and
PCLOUD_CLIENT_SECRET are set. A precondition check that can itself launch a
login flow is the wrong shape; resolveStoredAuth only asks whether a credential
is already on hand. Its own source comment notes it exists for "callers that
cannot await (React render paths, sync entrypoints)".

exitNotAuthenticated() is extracted, not duplicated. getAuthenticatedAPI
and requireStoredAuth now share one message. The return in
getAuthenticatedAPI's catch is load-bearing β€” TypeScript only treats a call as
flow-terminating for specific declaration forms, and an arrow-const isn't one, so
without it the function trips TS2366.

Testing

Three tests in src/browse.test.ts, spawning the CLI with HOME pointed at an
empty temp directory (TokenStore reads os.homedir()) and the credential env
vars stripped. cwd goes to that temp directory too, so dotenv.config() cannot
pick up a .env from the repo root and hand the subprocess the credentials the
test is withholding.

Verified in both directions β€” with the fix, 3 pass; with requireStoredAuth()
commented out, 2 fail.

That second run mattered. On the first draft, only the message assertion failed
without the fix: the exit-code and alternate-screen assertions passed anyway,
because mounting the browser in a non-TTY crashes ink-picture's terminal query
and exits non-zero for an unrelated reason. Both were rewritten β€” the surviving
ordering test now asserts that crash stack is absent, which is what actually
distinguishes "refused before mounting" from "died inside the render".

npm run typecheck clean. Worth noting it caught what vitest could not: spreading
process.env into an object literal drops its index signature, so
delete env.PCLOUD_CLIENT_ID failed to compile while the tests passed at runtime.

- Add requireStoredAuth() in src/cli.ts, using resolveStoredAuth (not resolveAuth) so the check never triggers an interactive OAuth round-trip when PCLOUD_CLIENT_ID/_SECRET are set
- Call requireStoredAuth() in the browse command's .action() before startBrowse(), since checking inside the render happens after the alternate screen is up and the error is written to a buffer torn down microseconds later
- Refactor the not-authenticated message into exitNotAuthenticated() and reuse it from getAuthenticatedAPI()
- Add src/browse.test.ts with runBrowseLoggedOut(), which spawns the CLI in a fresh HOME with no PCLOUD_* env vars and asserts the auth error prints while the ink-picture/TerminalInfo render-crash stack is absent
@kud
kud marked this pull request as ready for review July 31, 2026 14:36
@kud
kud merged commit c109d21 into main Jul 31, 2026
1 check passed
@kud
kud deleted the fix/browse-auth-check branch July 31, 2026 14:36
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