Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

es-client: non-JSON error responses report "Body is unusable" instead of the HTTP status

Open Beginner friendly
#686 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
75/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
nodejs, typescript

Research direction

The bug is in src/lib/es-client.ts lines 125-132. Start by reading the file to understand the request flow. The fix is to read the response body as text first, then try to parse it as JSON, similar to how kibana-client.ts and cloud-client.ts handle it. Write a test to reproduce the error using a mock HTTP server that returns a non-JSON error response, then verify the fix returns the correct status code and body.

Written by the indexing model from the issue text.

Description

Summary

Any non-2xx response whose body is not valid JSON surfaces as Error: Body is unusable: Body has already been read, losing the status code. That is undici's TypeError — the error branch of
EsClient.request reads the body twice. Deterministic, not timing-dependent.

The usual way to hit it is a reverse proxy in front of Elasticsearch: proxy_read_timeout expiring
on a long ES|QL query returns an nginx HTML 504, and the CLI reports Body is unusable instead of
504 — pointing suspicion at the client rather than the proxy.

Root cause

src/lib/es-client.ts#L125-L132:

if (!response.ok) {
  let body: unknown
  try {
    body = await response.json()   // consumes the stream
  } catch {
    body = await response.text()   // ← throws: body already consumed
  }
  throw new EsResponseError(response.status, body)
}

response.json() disturbs the body even when it rejects, so the catch branch can never read it.

Reproduction

cat > elasticrc-local.yml <<'YAML'
current_context: local
contexts:
  local:
    elasticsearch:
      url: http://127.0.0.1:9999
      auth: { api_key: ZmFrZTpmYWtl }
YAML
chmod 0600 elasticrc-local.yml
export ELASTIC_CLI_CONFIG_FILE="$PWD/elasticrc-local.yml" NO_PROXY=localhost,127.0.0.1

# stand in for "ES behind a proxy that timed out"
node -e 'require("http").createServer((q,s)=>{s.writeHead(504,{"content-type":"text/html"});s.end("<html><body>504 Gateway Time-out</body></html>")}).listen(9999,"127.0.0.1")' &

elastic es esql query --query 'FROM x | LIMIT 1' --format txt
# Error: Body is unusable: Body has already been read     (exit 1)

Same for 502, 503, text/plain and empty bodies. A JSON error body works correctly
(Error: illegal_argument_exception: …). Reproduced on v0.4.0 and v0.5.0, Node v26.8.1,
Linux x86_64, against Elasticsearch 9.5.3.

Suggested fix

Read once, then parse — as kibana-client.ts#L212
and cloud-client.ts#L75
already do in this repo:

const text = await response.text()
let body: unknown
try {
  body = JSON.parse(text)
} catch {
  body = text
}
throw new EsResponseError(response.status, body)

EsResponseError's message should also carry the status, since a string body currently renders as
bare HTML. Happy to open a PR with tests for an HTML and an empty error body.

Context

Introduced by #281 (native-fetch EsClient); @elastic/transport handled it correctly. Present in
every release since v0.2.0 and on main today. #588 fixed the same blind-JSON.parse mistake on
the success branch of this file — this is the error branch.

Dominant language
TypeScript
Stars
43
Forks
24
Avg merge
1d 5h
Merged PRs (30d)
61

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from elastic/cli

All issues in elastic/cli

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.