Skip to content

Error bodies on non-JSON endpoints are reported as "{}" (e.g. "Not Found: {}" for a missing blob) #1263

Description

@petfold

What happens

On endpoints that request a non-JSON responseType, the error body is parsed with that same responseType, so the server's explanation never reaches error.message. Every failure on those endpoints reads as {}:

bee.data.download(missing)   -> BeeResponseError: "Not Found: {}"                 (404)
bee.chunk.download(missing)  -> BeeResponseError: "Internal Server Error: {}"     (500)
bee.stamp.get(id)            -> BeeResponseError: "Internal Server Error: {"code":500,"message":"something specific"}"

The last line is a JSON endpoint, where it works as intended. The first two are arraybuffer endpoints, where the message is lost.

The body is not gone — it is on error.responseBody as an ArrayBuffer — but the caller has to know to decode it, and error.message is what reaches logs and error reporting.

It defeats a guard that is already there

throwHttpError deliberately falls back to the bare status text when a response has no body:

// src/utils/http.ts
async function throwHttpError(method, url, res, responseType) {
  const errBody = await toBeeResponse(res, responseType).catch(() => ({ data: undefined }));
  const bodyMsg = typeof errBody.data === 'string' ? errBody.data : JSON.stringify(errBody.data);
  const message = bodyMsg && bodyMsg !== 'undefined' ? `${res.statusText}: ${bodyMsg}` : res.statusText;
  ...
}

That bodyMsg !== 'undefined' check cannot fire for a binary response, because JSON.stringify(<ArrayBuffer>) is "{}" whether the buffer is empty or holds the whole error:

/chunks 500, JSON body   -> "Internal Server Error: {}"
/chunks 500, empty body  -> "Internal Server Error: {}"

Byte-identical, so a caller cannot even tell whether the node said anything.

Reproduction

bee-js 13.0.0, Node 22. No Bee node needed:

import { createServer } from "node:http";
import { Bee } from "@ethersphere/bee-js";

const server = createServer((req, res) => {
  res.writeHead(404, { "content-type": "application/json" });
  res.end(JSON.stringify({ code: 404, message: "not found" }));
});
await new Promise((r) => server.listen(8799, r));

try {
  await new Bee("http://127.0.0.1:8799").data.download("00".repeat(32));
} catch (e) {
  console.log(e.message);                                  // Not Found: {}
  console.log(new TextDecoder().decode(e.responseBody));   // {"code":404,"message":"not found"}
}
server.close();

api/bytes.ts and api/chunk.ts both pass responseType: 'arraybuffer' to download; 'blob' and 'stream' have the same problem.

Suggested fix

Error bodies are small and are text or JSON whatever the success response would have been, so they can be read independently of the request's responseType:

const raw = await res.text().catch(() => undefined);
let data;
try {
  data = raw ? JSON.parse(raw) : undefined;
} catch {
  data = raw;
}
const bodyMsg = typeof data === 'string' ? data : JSON.stringify(data);

This also restores the empty-body fallback, and puts something useful on responseBody for binary endpoints instead of an ArrayBuffer the caller must decode.

Why it matters

Bee distinguishes conditions a client must handle differently — a missing chunk, a retrieval that did not complete, a genuine internal error — and on the binary endpoints all of them arrive as {}. We are writing an SDK that reads feed updates by index, where "nothing written here yet" is the expected answer most of the time; we ended up decoding responseBody by hand to tell that apart from a node in trouble.

If a 500 is meant to be exceptional

It is reasonable to treat a 500 as "the node is in trouble, read its logs", and to care less about how its body is formatted. Two things make this worth fixing anyway.

Bee returns 500 read chunk failed for a chunk it cannot retrieve, which is the ordinary answer for a feed index nobody has written yet, so the path runs constantly against a perfectly healthy node. (Bee's OpenAPI documents 404 for that endpoint, so the status itself may be the bug; raised separately as ethersphere/bee#5624.)

And the same branch handles every non-2xx on these endpoints, including the 404s above. Whatever happens to the 500, a missing blob will still report Not Found: {} until the responseType is decoupled from the error body.

Minor, while in this code

BeeResponseError does not set name, so error.name is "Error" even though the class is BeeResponseError. instanceof works; the name is what most log lines print.

Environment

bee-js 13.0.0, @ethersphere/core-sdk 0.1.1, Node 22.23. Seen against Bee 2.8.2 (bee-factory) and reproduced against the stub server above.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions