Skip to content

fix(api): map the API's full error-code set to CLI error codes - #661

Merged
gulshngill merged 2 commits into
mainfrom
fix/server-error-code-map
Sep 20, 2026
Merged

gulshngill merged 2 commits into
mainfrom
fix/server-error-code-map

Conversation

@gulshngill

@gulshngill gulshngill commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The API returns a structured error body with a stable, machine-readable code on every error, and statusToErrorCode() prefers that code over the HTTP status. The CLI's SERVER_CODE_MAP only knew nine keys, five of which the API never sends, so most real codes leaked through as raw snake_case strings and every consumer keyed on ErrorCode silently missed them. Most visibly, a 401 arrives as unauthenticated (not unauthorized), so the Not logged in. Run: nansen login hint never fired.

After this change the CLI recognises the full set of codes documented on the API's error-handling page, the login hint is back for unauthenticated, and insufficient_credits gets its do-not-retry hint again, while unknown_field retains the API’s field-name guidance.

Changes

  • src/api.js
    • SERVER_CODE_MAP now keys on all 23 documented codes and is exported so tests can pin it. The five old keys (unauthorized, payment_required, unsupported_filter, validation_error, invalid_params) stay as aliases so older responses still map.
    • ErrorCode gains PLAN_UPGRADE_REQUIRED, GEO_BLOCKED, QUERY_TOO_LARGE, PAYLOAD_TOO_LARGE, METHOD_NOT_ALLOWED, CONFLICT — each has a distinct remediation that INVALID_PARAMS or TIMEOUT would misstate (e.g. query_too_large is "narrow the query", not "retry").
    • Field-level validation codes collapse onto existing client-side categories: missing_field → MISSING_PARAM, invalid_address_format → INVALID_ADDRESS, unknown_field → INVALID_PARAMS so the API’s typo/location guidance is preserved without an unrelated token/chain filter hint; the rest → INVALID_PARAMS. query_timeout → TIMEOUT, upstream_unavailable → SERVICE_UNAVAILABLE, internal_error → SERVER_ERROR.
    • Status fallbacks for bodies without a code: 405, 408, 409, 413, 451 now map to the matching new codes instead of the generic 4xx default.
    • Retry decisions are untouched: they stay keyed on HTTP status (429/500/502/503/504), so the remap cannot change which responses are retried.
  • src/__tests__/error-codes.test.js (new): table-driven cases for all 23 codes, a divergence check that fails if a documented code is missing from the map (or a stray key appears), legacy-alias fallback, unknown-code passthrough, server code winning over prose, accurate unknown_field remediation, structured server-side missing_field output, the 401 unauthenticated login hint with and without a configured key, and retry semantics per code (retried on 429/500/503/504, not retried on 403/409/422/451).
  • README.md: PLAN_UPGRADE_REQUIRED and GEO_BLOCKED rows in the critical error-code table, plus a pointer to the public error-handling page.
  • .changeset/server-error-code-map.md: patch.

Server missing_field maps to MISSING_PARAM but remains a structured JSON error even in an interactive terminal; only local usage banners use the plain-text TTY rendering path.

Validation

 Test Files  77 passed (77)
      Tests  3260 passed | 2 skipped (3262)

npm run lint — clean (no output from eslint .).

Live check (read-only, no credits spent): an invalid key against api.nansen.ai returns HTTP 401 with a plain {"message": ...} body and no code, which the existing status fallback already handled; the unauthenticated code is the documented body shape this change covers.

Checklist

  • Tests pass (npm test)
  • src/schema.json updated if new commands or flags were added — none added
  • README.md updated if new top-level commands or categories were added — error-code table extended
  • Changeset added (.changeset/) — only required for user-facing changes (new commands, flags, bug fixes, breaking changes)

🤖 Generated with Claude Code

The API returns a stable, machine-readable `code` on every error body, and
statusToErrorCode() prefers it over the HTTP status. SERVER_CODE_MAP only
knew nine keys, five of which the API never sends, so most codes leaked
through as raw strings and a 401 `unauthenticated` no longer triggered the
"Not logged in. Run: nansen login" hint.

- Key SERVER_CODE_MAP on every code documented at
  https://docs.nansen.ai/getting-started/error-handling, keeping the old
  keys as aliases so older responses still map.
- Add PLAN_UPGRADE_REQUIRED, GEO_BLOCKED, QUERY_TOO_LARGE, PAYLOAD_TOO_LARGE,
  METHOD_NOT_ALLOWED and CONFLICT to ErrorCode, with matching status
  fallbacks for bodies that carry no code.
- Retry decisions are unchanged (keyed on HTTP status); tests pin that,
  along with the 23-code table, the legacy aliases, unknown-code
  passthrough and the login hint.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@nansen-pr-reviewer

nansen-pr-reviewer Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

pr-reviewer Summary for #75f9249

📝 1 finding

Review completed. Please address the findings below.

Findings by Severity

Severity Count
🔵 Low 1

Review effort: 2/5 (Simple)

Summary

This PR correctly wires the full set of documented API error codes into the CLI. The SERVER_CODE_MAP expansion, new ErrorCode constants, status-fallback additions to statusToErrorCode, and the isUsageError guard (status != null) are all well-reasoned and consistent with existing patterns. Tests are thorough — the divergence check that fails if a documented code goes unmapped is a particularly nice safety net.

Findings (1 low)

src/api.js — PR description inaccuracy for unknown_field

Severity: low

The PR description states:

unknown_field → UNSUPPORTED_FILTER (the same class of response the prose fallback already mapped there)

But the actual code maps unknown_field: ErrorCode.INVALID_PARAMS (line 148 of src/api.js), and the test at line 21 of error-codes.test.js correctly asserts INVALID_PARAMS. The prose fallback at line 206 (messageLower.includes('field') && messageLower.includes('not recognized')) would have returned UNSUPPORTED_FILTER for such messages when there was no structured code, but now that unknown_field is mapped directly it goes to INVALID_PARAMS — a deliberate and sensible change.

The description is misleading and may confuse future reviewers who audit what unknown_field does. No code change needed, but the PR description (or an inline comment) could clarify that this mapping was intentionally changed from the prose-sniffed UNSUPPORTED_FILTER to INVALID_PARAMS.

Action: Update the PR description to read unknown_field → INVALID_PARAMS, or note the mapping change explicitly.


Token usage: 3,232 input, 3,996 output, 428,245 cache read, 32,411 cache write | Usage Guide

New pushes are reviewed automatically with a 10-minute cooldown between reviews. To request a review at any time, comment @nansen-pr-reviewer re-review.

nansen-pr-reviewer[bot]
nansen-pr-reviewer Bot previously approved these changes Sep 19, 2026

@nansen-pr-reviewer nansen-pr-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Auto-approved

This PR was automatically approved because:

  • Claude recommends approval
  • Claude assessed this as a moderate effort change
  • The effort level is within the auto-approval threshold of 2
  • No high or critical issues were detected

If you have any concerns, please request a manual review.

@gulshngill

Copy link
Copy Markdown
Contributor Author

Merge position: 2 of 9. Merge after #665.

Touches src/api.js and README.md, which #662 and #668 also touch. Landing this before both keeps their rebases to one file each.

@nansen-pr-reviewer nansen-pr-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Auto-approved

This PR was automatically approved because:

  • Claude recommends approval
  • Claude assessed this as a moderate effort change
  • The effort level is within the auto-approval threshold of 2
  • No high or critical issues were detected
  • Review comment contains non-blocking feedback

If you have any concerns, please request a manual review.

@gulshngill

Copy link
Copy Markdown
Contributor Author

Addressed the reviewer note: the PR description now documents unknown_field → INVALID_PARAMS and explains that this preserves the API’s typo/location guidance instead of adding the unrelated token/chain filter hint.

@gulshngill
gulshngill merged commit 251ffc7 into main Sep 20, 2026
9 checks passed
@gulshngill
gulshngill deleted the fix/server-error-code-map branch September 20, 2026 05:08
@github-actions github-actions Bot mentioned this pull request Sep 18, 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