Skip to content

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

Open
wyattjoh wants to merge 9 commits into
mainfrom
wyattjoh/api-error-classification
Open

refactor(errors): expose structured Clerk error fields on ApiError subclasses#288
wyattjoh wants to merge 9 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.

Test plan

  • bun run typecheck
  • bun run test
  • CI green

wyattjoh added 9 commits May 12, 2026 20:52
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
Copy link
Copy Markdown
Contributor Author

wyattjoh commented May 13, 2026

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 13, 2026

⚠️ No Changeset found

Latest commit: 93d7a24

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

📝 Walkthrough

Walkthrough

This pull request refactors HTTP API error handling across the CLI by introducing factory methods and structured field extraction. ApiError now exposes code, longMessage, meta, and clerkTraceId properties parsed from Clerk-style error envelopes. All API clients (PlapiError, FapiError, BapiError) gain static fromBody and fromResponse factories to standardize error construction. The CLI's formatApiBody function now accepts ApiError objects and uses the parsed fields to format output; runProgram builds JSON error payloads directly from those fields. Tests are updated to construct mocked errors via factories and to expect newly formatted error messages (HTTP phrases instead of generic strings).

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
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.
Title check ✅ Passed The title clearly and accurately describes the main refactoring: exposing structured Clerk error fields (code, long_message, meta, clerk_trace_id) on ApiError and its subclasses.
Description check ✅ Passed The description comprehensively explains the refactoring goals, implementation approach (error envelope parsing, factory constructors), and provides a test plan checklist.

✏️ 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 on lines +200 to +202
const envelope = parsed as ClerkErrorEnvelope;
const first = envelope.errors?.[0];
if (!first) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle null JSON bodies before envelope access.

At Line 200, parsed is cast and then envelope.errors?.[0] is accessed. If the API returns JSON null, this throws a TypeError and bypasses 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;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const envelope = parsed as ClerkErrorEnvelope;
const first = envelope.errors?.[0];
if (!first) {
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;
if (!first) {
🤖 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
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.

@wyattjoh wyattjoh changed the title wyattjoh/api error classification refactor(errors): expose structured Clerk error fields on ApiError subclasses May 13, 2026
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