Skip to content

fix: do not require a keychain token read for commands that do not need auth#2947

Draft
spenserhale wants to merge 1 commit into
Automattic:trunkfrom
spenserhale:fix/skip-token-read-for-tokenless-commands
Draft

fix: do not require a keychain token read for commands that do not need auth#2947
spenserhale wants to merge 1 commit into
Automattic:trunkfrom
spenserhale:fix/skip-token-read-for-tokenless-commands

Conversation

@spenserhale

@spenserhale spenserhale commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes a startup crash affecting commands that don't require authentication, in sessions where the OS keychain is unavailable.

The bug: on macOS over SSH, every vip command — including vip -h — fails with:

✕ Please contact VIP Support with the following information:
Error: An unknown error occurred.
Error:  Unexpected error

Root cause: rootCmd() in src/bin/vip.js calls Token.get() unconditionally, before checking whether the invocation needs auth at all. Reading the stored token from the macOS keychain requires an ACL authorization prompt that can only be shown in a graphical session, so over SSH keytar rejects with a bare native error (no stack frames). The crash happens before argument parsing, so --debug can't produce any more information.

The fix:

  • Compute the argv classification flags first and skip the token read entirely for invocations that never consult it: help/-h/--help, -v/--version, logout, dev-env without an env argument, and custom deploys keyed by WPVIP_DEPLOY_TOKEN. vip -h now performs zero keychain access.
  • When a token is needed and the keychain read fails, exit with an actionable message (keychain locked/unavailable, common over SSH, with remediation hints and a DEBUG=@automattic/vip:* pointer) instead of the opaque crash.
  • Token.uuid() (analytics) degrades to a stable per-process UUID when the keychain is unavailable, so telemetry cannot crash an otherwise-working command.
  • vip logout tolerates an unreadable keychain: it skips the server-side /logout call when there is no readable valid token and always completes local cleanup.
  • doesArgvHaveAtLeastOneParam() now stops scanning at the -- terminator, so e.g. vip wp <env> -- --help is no longer misclassified as top-level help.
  • The classification helpers (doesArgvHaveAtLeastOneParam() and the token-read predicate) are extracted into a pure src/lib/cli/startup-args.ts module with unit tests covering the -- terminator handling and the full skip-flag matrix.

Related: pairs with #2946, which adds VIP_CLI_TOKEN authentication for these environments; the two are independent and address different use cases.

Changelog Description

Fixed

  • Fixed a crash ("Unexpected error") when running commands that do not require authentication (such as vip -h, vip -v, and local vip dev-env commands) in sessions where the OS keychain is unavailable, such as over SSH.

Pull request checklist

Steps to Test

  1. Check out PR.
  2. Run npm run build.
  3. Run DEBUG=@automattic/vip:keychain node ./dist/bin/vip.js -h — help prints and the debug output shows no keychain activity.
  4. Run node ./dist/bin/vip.js -v and node ./dist/bin/vip.js dev-env info — no keychain access required.
  5. Full reproduction: SSH into a macOS machine that has VIP-CLI authenticated. Before this change vip -h crashes with "Unexpected error"; with this change it prints help, and auth-requiring commands (e.g. vip app list) print an actionable keychain message instead of crashing.

…ed auth

On macOS over SSH (and other headless sessions), reading the stored token
from the OS keychain fails because the keychain ACL authorization prompt
requires a GUI. On trunk, rootCmd() in src/bin/vip.js called Token.get()
unconditionally before checking whether the invocation even needs auth, so
`vip -h`, `vip -v`, `vip logout`, and local `vip dev-env ...` crashed with an
opaque "Unexpected error" even though none of them require a token. keytar's
native rejection carries no stack frames, and the crash happened before
argument parsing so --debug could not help.

Changes:
- vip.js: compute the argv classification flags before fetching the token, and
  add resolveToken() which skips Token.get() entirely for commands that do not
  consult the token (help, version, logout, tokenless dev-env, custom-deploy
  with key). When the token is actually needed and the keychain read fails,
  surface an actionable error explaining the SSH/headless keychain limitation
  instead of crashing.
- vip.js: stop doesArgvHaveAtLeastOneParam() from scanning past the `--`
  terminator so `vip wp <env> -- --help` is not misclassified as top-level help.
- token.ts: Token.uuid() now tolerates an unreachable keychain by returning a
  stable per-process UUID, so analytics cannot crash an otherwise-working
  command.
- logout.ts: tolerate an unavailable keychain by skipping the server-side
  logout call when the stored token cannot be read or is invalid, and by
  wrapping Token.purge() so local cleanup always completes.
- startup-args.ts: extract doesArgvHaveAtLeastOneParam() and isTokenReadRequired()
  into a pure, unit-tested module so the startup classification logic is covered
  outside the bin script.
@spenserhale spenserhale force-pushed the fix/skip-token-read-for-tokenless-commands branch from f0c3695 to 0de1bcd Compare July 10, 2026 09:02
@sonarqubecloud

Copy link
Copy Markdown

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