Skip to content

fix(errors): keep lower-snake fail-envelope codes in error.code (#117) - #120

Merged
karlwaldman merged 1 commit into
mainfrom
fix/117-lowercase-fail-codes
Sep 14, 2026
Merged

karlwaldman merged 1 commit into
mainfrom
fix/117-lowercase-fail-codes

Conversation

@karlwaldman

Copy link
Copy Markdown
Member

Closes #117

What was wrong (re-verified 2026-09-14)

MACHINE_CODE = /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/ (src/errors.ts, from #114) accepted only upper-snake. Production also sends lower-snake codes in the JSend fail envelope, with the sentence in data.message. On main those came back with error.code === "HTTP_ERROR" (or the class default), and the machine code was lost.

Live bodies captured from api.oilpriceapi.com with the test key:

Request HTTP data
GET /v1/prices/latest?by_code=NOT_A_REAL_CODE_XYZ 400 {"error":"invalid_code","message":"Code 'NOT_A_REAL_CODE_XYZ' not found. ...", "suggestions":[], ...}
GET /v1/prices/latest with 11 BRENT_FUTURES_* codes 400 {"error":"invalid_request","message":"Futures multi-code latest is limited to 10 codes per request. You sent 11 (11 futures, 0 spot).","hint":"..."}
GET /v1/prices/latest with 21 codes 400 {"error":"Too many commodity codes requested (max: 20, requested: 21)"}: a sentence, must stay the message
GET /v1/fuel-surcharge/nope/latest 404 {"error":"Unknown carrier 'nope'. Covered carriers: ..."}: sentence
GET /v1/subscriptions/<unknown uuid> 404 canonical nested {"error":{"code":"NOT_FOUND",...}}

Which tokens the API sends as data.error (oilpriceapi-api origin/main)

  • Upper-snake: VALIDATION_ERROR, WATCH_LIMIT, INTERVAL_FLOOR.
  • Lower-snake: invalid_code (api_validations.rb:76), no_price_data (prices_controller.rb:536, :856), invalid_request (:689), commodity_discontinued (:865), commodity_unavailable (:875).
  • No fail envelope sends a bare single word. The only single-word error values in app/ are 'Unauthorized' (top-level render json: { error: 'Unauthorized' } in admin/billing/analytics controllers) and 'not_available' (top-level, forecasts). Neither is inside {status: "fail", data}, so neither reaches this code path. A bare word like forbidden therefore stays the message.

Cross-checked against python-sdk #148, which uses the same rule: a snake-case token, all upper or all lower, ASCII alphanumerics joined by _, starting with a letter.

Fix

const MACHINE_CODE = /^(?:[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+|[a-z][a-z0-9]*(?:_[a-z0-9]+)+)$/;

A code-shaped value that contains the configured API key is never used as code. code is not redacted, and the fixture key (fixture_key_not_a_real_credential) is itself lower-snake, so without this guard the new rule would have copied it into error.code. It stays the redacted message. Redaction itself is untouched. The nested canonical error object still wins.

TDD evidence

Red: new tests against origin/main (7fd722b) src/errors.ts

$ npx vitest run tests/fail-envelope-errors.test.ts
     × 400 invalid_code: the code becomes code, the sentence becomes message
     × 400 invalid_request (live) keeps code, message and hint
     × 404 no_price_data keeps the code on a NotFoundError
     × a lower-snake code with no message is both the code and the fallback message
 Test Files  1 failed (1)
      Tests  4 failed | 20 passed (24)

 FAIL  tests/fail-envelope-errors.test.ts > #117 lower-snake fail-envelope codes reach error.code > 400 invalid_code: the code becomes code, the sentence becomes message
AssertionError: expected 'HTTP_ERROR' to be 'invalid_code' // Object.is equality
Expected: "invalid_code"
Received: "HTTP_ERROR"

On old code these new negative-control tests already pass, and must keep passing: the live sentence, forbidden, Unauthorized, Invalid_Code, invalid-code, 1st_error, _invalid, invalid_, key-in-value redaction, and nested-object precedence.

Green

$ npx vitest run tests/fail-envelope-errors.test.ts
      Tests  24 passed (24)

$ npx vitest run
 Test Files  55 passed (55)
      Tests  827 passed | 1 skipped (828)
Type Errors  no errors

Baseline on 7fd722b was 812 passed | 1 skipped.

  • npx tsc --noEmit: 0
  • npm run lint: 0
  • npx tsc --noEmit -p tsconfig.typecheck.json: 0
  • npm run build: 0
  • npm run storefront:check: validated 39 Node public surfaces
  • npm run check:secrets: 0
  • npm run snippets:check: 0

No version bump and no CHANGELOG heading; release-note text is below.

Release note (2.0.0)

  • Changed: a fail-envelope data.error in lower-snake form (invalid_code, invalid_request, no_price_data, commodity_discontinued, commodity_unavailable) is now error.code, with data.message as error.message. These errors previously carried code: "HTTP_ERROR" (or the class default). Code that branched on "HTTP_ERROR" for these responses should branch on the specific code.

No file in common with #112's PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_015ao5paex73xXvuM424Libo

#114 read `data.error` as a machine code only when it was upper-snake. The
API also sends lower-snake codes in the fail envelope (`invalid_code` from
api_validations.rb; `invalid_request`, `no_price_data`,
`commodity_discontinued`, `commodity_unavailable` from prices_controller.rb),
each with its sentence in `data.message`, so the code was dropped.

A `data.error` is now a code when it is a snake-case token in one case,
upper or lower, with at least one underscore. Sentences, bare words and
mixed case stay the message. A value containing the configured key is never
copied into `code`, which is not redacted. The nested canonical `error`
object still takes precedence, and redaction is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015ao5paex73xXvuM424Libo
@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 1132bf6a-471a-4ed0-8daf-40d20e91cd82


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@karlwaldman
karlwaldman merged commit 3db97e2 into main Sep 14, 2026
8 checks passed
@karlwaldman
karlwaldman deleted the fix/117-lowercase-fail-codes branch September 14, 2026 12:07
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.

[P2][errors] lower-snake fail codes (invalid_code, no_price_data, invalid_request) are dropped from error.code

1 participant