Skip to content

fix(provider): extract nested error.message from OpenAI-shaped bodies - #44057

Open
chandlerm923 wants to merge 1 commit into
anomalyco:devfrom
chandlerm923:fix/provider-error-nested-message
Open

fix(provider): extract nested error.message from OpenAI-shaped bodies#44057
chandlerm923 wants to merge 1 commit into
anomalyco:devfrom
chandlerm923:fix/provider-error-nested-message

Conversation

@chandlerm923

Copy link
Copy Markdown

Issue for this PR

Closes #36410

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

message() in packages/opencode/src/provider/error.ts extracts a fallback error string from the response body with:

const errMsg = body.message || body.error || body.error?.message

For the common OpenAI-shaped body {"error":{"message":"..."}}, body.error is a truthy object, so it wins the || chain before body.error.message is ever read. The typeof errMsg === "string" check then fails and the real message is dropped — users see a generic status line or a raw JSON dump instead of the actual reason (rate limit, quota, invalid key, etc.).

There was an earlier PR for this (#36411) that implemented essentially the same fix, but it went stale and was closed by the automated cleanup bot (>1 month old, no reactions) rather than rejected on the merits — no maintainer raised any objection to the approach there.

This PR reorders the extraction to type-check each field explicitly and prefer the nested string first:

const errMsg =
  (typeof body.error?.message === "string" ? body.error.message : undefined) ??
  (typeof body.message === "string" ? body.message : undefined) ??
  (typeof body.error === "string" ? body.error : undefined)

How did you verify your code works?

  • Added two tests to packages/opencode/test/provider/error.test.ts: one for the nested {error:{message}} shape, one for the pre-existing top-level string error shape (to make sure that path still works).
  • bun test test/provider/error.test.ts — both new tests pass.
  • bun turbo typecheck — 30/30 packages pass.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

body.error is a truthy object for the common {"error":{"message":...}}
shape, so the old field order (body.message || body.error ||
body.error?.message) always short-circuited before reaching the nested
string, and the typeof guard then dropped it. Users saw the raw
response body or a generic status line instead of the real reason
(rate limit, quota, bad key).
@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference; please use your judgment.

  1. packages/opencode/src/provider/error.ts:52 — An empty-string nested message breaks the ?? chain: { "error": { "message": "" }, "message": "real reason" } makes the first operand "" (not nullish), so coalescing never reaches body.message, and the outer if (errMsg) then discards it — net result: no extraction at all, whereas the old code returned "real reason". Why it matters: some gateways emit empty nested messages with a usable top-level one. Suggestion: coerce empties to undefined before coalescing, e.g. (s) => (typeof s === "string" && s.trim() ? s : undefined) applied per candidate.

  2. packages/opencode/src/provider/error.ts:51 — Precedence flip worth noting: previously body.message won over body.error; now nested error.message always wins. That's right for OpenAI-shaped bodies, but providers returning both fields with different texts will surface a different message than before. Why it matters: users/tests relying on the old text see a change. Suggestion: fine to keep — just call it out in the PR body/changelog.

  3. packages/opencode/test/provider/error.test.ts:6 — Missing cases: (a) both message and error.message present (locks in the new precedence), (b) { message } only, (c) the empty-string nested message from item 1 once fixed. Why it matters: these are exactly the branches the reorder touched. Suggestion: add the three table-driven cases.

  4. packages/opencode/src/provider/error.ts:55 (nit) — Common alternate shapes (detail from FastAPI-style APIs, errors[0].message) remain unhandled. Not blocking; refactoring to an ordered candidates list would make future additions one-liners.

— automated review (ox-alpha, round2)

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.

Provider errors shaped like OpenAI { error: { message } } skip the real message

2 participants