Skip to content

refactor(errors): expose structured Clerk error fields on ApiError subclasses#288

Merged
wyattjoh merged 10 commits into
mainfrom
wyattjoh/api-error-classification
May 21, 2026
Merged

refactor(errors): expose structured Clerk error fields on ApiError subclasses#288
wyattjoh merged 10 commits into
mainfrom
wyattjoh/api-error-classification

Conversation

@wyattjoh
Copy link
Copy Markdown
Contributor

@wyattjoh wyattjoh commented May 13, 2026

Summary

ApiError and its PlapiError / FapiError / BapiError subclasses now parse the Clerk error envelope at construction time and expose code, long_message, meta, and clerk_trace_id as typed instance fields. The base constructor uses the parsed message (or a short truncated fallback for non-JSON bodies) instead of the previous API error (<status>): <raw body> string, so default error.message rendering already shows the human-readable Clerk message. The global error handler in cli-program.ts reads those fields directly rather than re-parsing the body, which collapses the existing ad-hoc body-string branching.

Each subclass gains a fromResponse(response: Response) async factory that reads the body once and constructs the error, replacing the manual response.text() + new XError(...) pattern at every PLAPI / FAPI / KEYLESS / BAPI / users call site. The existing fromBody helpers stay for tests and fixtures that already have the body string in hand.

The parser also guards against JSON bodies whose top level is null or another non-object value, so a response body of "null" no longer crashes the global handler with a TypeError and falls back to the truncated-body path instead.

Test plan

  • bun run typecheck
  • bun run test
  • CI green

@wyattjoh
Copy link
Copy Markdown
Contributor Author

wyattjoh commented May 13, 2026

Stack: api-errors-and-deploy

Part of a stacked-prs chain. Do not merge manually.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 13, 2026

⚠️ No Changeset found

Latest commit: 4ab06d7

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 476d8a14-2dd0-440c-8a67-639bdd92f672

📥 Commits

Reviewing files that changed from the base of the PR and between 162f269 and 4ab06d7.

📒 Files selected for processing (2)
  • packages/cli-core/src/lib/errors.test.ts
  • packages/cli-core/src/lib/errors.ts

📝 Walkthrough

Walkthrough

This PR centralizes parsing of Clerk-style API error envelopes and exposes structured fields on ApiError (code, longMessage, meta, clerkTraceId). It adds fromBody/fromResponse factories for PlapiError, FapiError, and BapiError, updates API clients to throw factory-built errors (deferring body reads until after status checks), rewrites CLI formatApiBody to accept ApiError and format structured or verbose output, and updates tests/mocks to use the new factories and adjust expected messages.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main refactoring: exposing structured Clerk error fields on ApiError subclasses.
Description check ✅ Passed The description thoroughly explains the changes, including new structured fields, refactored error handling, factory methods, and a comprehensive test plan.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/cli-core/src/lib/errors.ts`:
- Around line 200-202: The code currently casts parsed to ClerkErrorEnvelope and
reads envelope.errors?.[0] which will throw if the API returned JSON null; add a
guard that checks parsed == null (or parsed === null) before casting/reading so
you early-fallback instead of accessing properties on null. Concretely, update
the logic around the variables parsed, envelope and first (the lines creating
const envelope = parsed as ClerkErrorEnvelope; const first =
envelope.errors?.[0];) to first check if parsed == null and handle the
fallback/error path, or coerce envelope to a safe object (e.g., const envelope =
parsed as ClerkErrorEnvelope | null; if (!parsed || !envelope?.errors?.[0]) {
... }) so you never access .errors on a null parsed value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: da47d176-356e-4745-8664-aaf56f760dac

📥 Commits

Reviewing files that changed from the base of the PR and between da59716 and 93d7a24.

📒 Files selected for processing (18)
  • packages/cli-core/src/cli-program.test.ts
  • packages/cli-core/src/cli-program.ts
  • packages/cli-core/src/commands/api/bapi.ts
  • packages/cli-core/src/commands/config/pull.test.ts
  • packages/cli-core/src/commands/config/push.test.ts
  • packages/cli-core/src/commands/config/schema.test.ts
  • packages/cli-core/src/commands/doctor/doctor.test.ts
  • packages/cli-core/src/commands/env/pull.test.ts
  • packages/cli-core/src/commands/link/index.test.ts
  • packages/cli-core/src/commands/users/create.test.ts
  • packages/cli-core/src/commands/users/interactive/instance-context.ts
  • packages/cli-core/src/lib/bapi-command.test.ts
  • packages/cli-core/src/lib/errors.test.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/fapi.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/lib/token-exchange.test.ts

Comment thread packages/cli-core/src/lib/errors.ts
@wyattjoh wyattjoh changed the title wyattjoh/api error classification refactor(errors): expose structured Clerk error fields on ApiError subclasses May 13, 2026
@wyattjoh wyattjoh force-pushed the wyattjoh/api-error-classification branch 2 times, most recently from bf05f3e to ffb869f Compare May 21, 2026 06:40
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/cli-core/src/lib/errors.ts (1)

200-202: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Handle null JSON bodies before accessing envelope properties.

If the response body is the JSON string "null", JSON.parse succeeds but parsed is null. Line 201's envelope.errors?.[0] will throw a TypeError when trying to access .errors on null (the optional chaining ?.[0] doesn't protect the initial .errors access).

Proposed fix
+  if (parsed === null || typeof parsed !== "object") {
+    return fallback(truncateBody(body));
+  }
+
   const envelope = parsed as ClerkErrorEnvelope;
-  const first = envelope.errors?.[0];
+  const first = Array.isArray(envelope.errors) ? envelope.errors[0] : undefined;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli-core/src/lib/errors.ts` around lines 200 - 202, The code assumes
JSON.parse returned an object and does const envelope = parsed as
ClerkErrorEnvelope and then accesses envelope.errors?.[0], which will throw if
parsed is null; update the logic to first check parsed !== null (or parsed ==
null) before casting/using envelope, e.g. treat parsed as ClerkErrorEnvelope |
null and if parsed == null handle the empty/null body case (return a sensible
default or error) or use a guarded access like if (parsed && (parsed as
ClerkErrorEnvelope).errors) to safely compute first; adjust any downstream uses
of envelope/first accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@packages/cli-core/src/lib/errors.ts`:
- Around line 200-202: The code assumes JSON.parse returned an object and does
const envelope = parsed as ClerkErrorEnvelope and then accesses
envelope.errors?.[0], which will throw if parsed is null; update the logic to
first check parsed !== null (or parsed == null) before casting/using envelope,
e.g. treat parsed as ClerkErrorEnvelope | null and if parsed == null handle the
empty/null body case (return a sensible default or error) or use a guarded
access like if (parsed && (parsed as ClerkErrorEnvelope).errors) to safely
compute first; adjust any downstream uses of envelope/first accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4efb2f17-e7bc-4f87-8a22-77dced2a9d78

📥 Commits

Reviewing files that changed from the base of the PR and between bf05f3e and ffb869f.

📒 Files selected for processing (18)
  • packages/cli-core/src/cli-program.test.ts
  • packages/cli-core/src/cli-program.ts
  • packages/cli-core/src/commands/api/bapi.ts
  • packages/cli-core/src/commands/config/pull.test.ts
  • packages/cli-core/src/commands/config/push.test.ts
  • packages/cli-core/src/commands/config/schema.test.ts
  • packages/cli-core/src/commands/doctor/doctor.test.ts
  • packages/cli-core/src/commands/env/pull.test.ts
  • packages/cli-core/src/commands/link/index.test.ts
  • packages/cli-core/src/commands/users/create.test.ts
  • packages/cli-core/src/commands/users/interactive/instance-context.ts
  • packages/cli-core/src/lib/bapi-command.test.ts
  • packages/cli-core/src/lib/errors.test.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/fapi.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/lib/token-exchange.test.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/cli-core/src/commands/env/pull.test.ts
  • packages/cli-core/src/commands/config/schema.test.ts

wyattjoh added 9 commits May 21, 2026 09:21
Replace formatApiBody(body: string) with formatApiBody(error: ApiError),
deleting extractApiErrorCode, extractApiErrors, and formatSingleError.
The new formatStructuredError reads code/message/meta directly from the
parsed ApiError instance. The agent path builds ApiErrorEntry inline from
structured fields and surfaces clerkTraceId in verbose human mode.
Tests updated to construct ApiError instances and reflect single-error
output for multi-error bodies.
@wyattjoh wyattjoh force-pushed the wyattjoh/api-error-classification branch from ffb869f to 162f269 Compare May 21, 2026 15:26
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/cli-core/src/lib/errors.ts (1)

200-201: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Guard null parsed JSON before reading errors.

At Line 200 and Line 201, JSON.parse(body) can return null (e.g. body is "null"), and envelope.errors will throw a TypeError, skipping your fallback path.

Proposed fix
-  const envelope = parsed as ClerkErrorEnvelope;
-  const first = envelope.errors?.[0];
+  if (parsed === null || typeof parsed !== "object") {
+    return fallback(truncateBody(body));
+  }
+
+  const envelope = parsed as ClerkErrorEnvelope;
+  const first = Array.isArray(envelope.errors) ? envelope.errors[0] : undefined;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/cli-core/src/lib/errors.ts` around lines 200 - 201, parsed can be
null if JSON.parse(body) returns null, causing envelope.errors access to throw;
update the logic around parsed/envelope (the parsed variable, ClerkErrorEnvelope
cast and the first = envelope.errors?.[0] line) to guard against null/non-object
parsed values by checking parsed !== null && typeof parsed === 'object' (or
using a safe fallback) before casting or accessing envelope.errors, and fall
back to the existing generic error handling when the check fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@packages/cli-core/src/lib/errors.ts`:
- Around line 200-201: parsed can be null if JSON.parse(body) returns null,
causing envelope.errors access to throw; update the logic around parsed/envelope
(the parsed variable, ClerkErrorEnvelope cast and the first =
envelope.errors?.[0] line) to guard against null/non-object parsed values by
checking parsed !== null && typeof parsed === 'object' (or using a safe
fallback) before casting or accessing envelope.errors, and fall back to the
existing generic error handling when the check fails.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d6b636d0-ed30-448f-95dd-6489e0483618

📥 Commits

Reviewing files that changed from the base of the PR and between ffb869f and 162f269.

📒 Files selected for processing (18)
  • packages/cli-core/src/cli-program.test.ts
  • packages/cli-core/src/cli-program.ts
  • packages/cli-core/src/commands/api/bapi.ts
  • packages/cli-core/src/commands/config/pull.test.ts
  • packages/cli-core/src/commands/config/push.test.ts
  • packages/cli-core/src/commands/config/schema.test.ts
  • packages/cli-core/src/commands/doctor/doctor.test.ts
  • packages/cli-core/src/commands/env/pull.test.ts
  • packages/cli-core/src/commands/link/index.test.ts
  • packages/cli-core/src/commands/users/create.test.ts
  • packages/cli-core/src/commands/users/interactive/instance-context.ts
  • packages/cli-core/src/lib/bapi-command.test.ts
  • packages/cli-core/src/lib/errors.test.ts
  • packages/cli-core/src/lib/errors.ts
  • packages/cli-core/src/lib/fapi.ts
  • packages/cli-core/src/lib/keyless.ts
  • packages/cli-core/src/lib/plapi.ts
  • packages/cli-core/src/lib/token-exchange.test.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/cli-core/src/commands/doctor/doctor.test.ts
  • packages/cli-core/src/commands/link/index.test.ts

@wyattjoh wyattjoh requested a review from rafa-thayto May 21, 2026 16:11
JSON.parse("null") and other non-object JSON values caused parseApiBody
to throw a TypeError when accessing envelope.errors. Add an early
fallback so the global error handler shows a truncated body instead of
crashing.
@wyattjoh wyattjoh merged commit 337e796 into main May 21, 2026
10 checks passed
@wyattjoh wyattjoh deleted the wyattjoh/api-error-classification branch May 21, 2026 19:21
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