Skip to content

fix(auth): preserve typed API error envelope when setup key verification fails#38

Open
lxcario wants to merge 1 commit into
TestSprite:mainfrom
lxcario:fix/setup-key-rejected-json-envelope
Open

fix(auth): preserve typed API error envelope when setup key verification fails#38
lxcario wants to merge 1 commit into
TestSprite:mainfrom
lxcario:fix/setup-key-rejected-json-envelope

Conversation

@lxcario

@lxcario lxcario commented Jun 25, 2026

Copy link
Copy Markdown

What

When setup --from-env --output json verifies an API key and the key is rejected (e.g. invalid or revoked), the JSON output is a bare {"error":"...string..."} instead of the typed envelope with code, nextAction, requestId, and details.

Why this matters

Machine consumers (coding agents, CI scripts) that JSON.parse(stderr) expect the typed envelope shape documented in DOCUMENTATION.md. A bare string error gives them no code to branch on, no nextAction to surface, and no requestId for support. Every other API error path in the CLI already emits the full typed envelope; this is the sole exception.

Reproduction

$ TESTSPRITE_API_KEY=sk-invalid testsprite setup --from-env --output json --yes --no-agent

# BEFORE (exit 3, bare string error):
{"error":"API key rejected by https://api.testsprite.com: API key is invalid or revoked. - did you mean to set TESTSPRITE_API_URL?"}

# AFTER (exit 3, full typed envelope):
{
  "error": {
    "code": "AUTH_INVALID",
    "message": "API key rejected by https://api.testsprite.com: API key is invalid or revoked. - did you mean to set TESTSPRITE_API_URL?",
    "nextAction": "API key is invalid or revoked. Generate a new one at https://www.testsprite.com/dashboard/settings/apikey.",
    "requestId": "cli_...",
    "details": { "reason": "malformed" }
  }
}

# Control: missing key (already correct, exit 5 with typed envelope):
$ testsprite setup --from-env --output json --yes --no-agent
{"error":{"code":"VALIDATION_ERROR","message":"TESTSPRITE_API_KEY is not set..."}}

Root cause

src/commands/auth.ts, runConfigure, line 157-168. The key-verification catch block wraps the original ApiError in a CLIError:

} catch (err) {
  ...
  throw new CLIError(
    `API key rejected by ${apiUrl}: ${message} - did you mean to set TESTSPRITE_API_URL?`,
    exitCode,
  );
}

CLIError has only message and exitCode. In index.ts, the CLIError path renders via output.error(err.message) which produces {"error":"...string..."} -- losing the full typed envelope that the ApiError path would have rendered.

Fix

When err is an ApiError, augment its .message with the endpoint context (so text-mode users still see which host rejected them), then re-throw it directly. The ApiError catch path in index.ts renders the full typed envelope. Non-ApiError throws (truly unexpected errors) still wrap in CLIError for a clean exit 3.

This mirrors how every other command path that catches and re-throws API errors already works (the error carries its own envelope; the CLI just sets exit code from it).

Tests

Added 1 regression test to src/commands/auth.test.ts asserting the thrown error is an ApiError with code: 'AUTH_INVALID', exitCode: 3, nextAction, and requestId intact. Fails on main before this fix; passes after.

  • Before (main): Tests 16 failed | 1359 passed | 72 skipped
  • After (this branch): Tests 16 failed | 1360 passed | 72 skipped (+1 new test)

The 16 failures are pre-existing and environment-specific (Windows path/line-ending), unrelated -- see #4.

Verification

  • npm test: same 16 pre-existing (environment-specific) failures as baseline, zero new
  • npm run typecheck: pass
  • npm run lint: pass

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