[BUG] ant beta:worker v1.9.0 — empty tool output → 400 "minimum string length is 1" → permanently wedged session (archived-redelivery loop)

Resolved 💬 2 comments Opened Jun 4, 2026 by blaw4040 Closed Jun 8, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Summary: In a self-hosted ant beta:worker poll session, any tool returning empty stdout permanently wedges the session. The worker POSTs a tool-result event with an empty text block; the API rejects it (400, events.0.content.0.text: minimum string length is 1); the client logs "not retrying" but leaves the turn unacknowledged; the session is archived server-side; subsequent posts fail with "Cannot send events to archived session"; and the poller re-claims the same archived work indefinitely. The process and systemctl status stay green throughout (liveness ≠ progress).
Severity: High for the self-hosted coding-agent use case. The trigger is not rare — it fires on any silently-succeeding command: mkdir, touch, cp, mv, rm, cd, export, chmod all emit no stdout on success. A coding agent hits these constantly; sessions wedge routinely. In our case it fired on a mkdir during the agent's preflight — its first action.
Build:

ant version 1.9.0
/usr/local/bin/ant — statically-linked x86-64 ELF, ~22 MB, build date 2026-05-19, BuildID[sha1]=c5009a84719a5148daadaf363df14692b659f4ca
Invocation: ant beta:worker poll --environment-id env_… --workdir … --unrestricted-paths

Repro: Run a self-hosted worker session where any tool returns empty output (e.g. a bash mkdir). Reproduces reliably.
Observed (verbatim):
level=ERROR msg="tool result send hit permanent 4xx; not retrying"
tool_use_id=sevt_…
error="POST \"https://api.anthropic.com/v1/sessions/sesn_…/events?beta=true\":
400 Bad Request {\"error\":{\"type\":\"invalid_request_error\",
\"message\":\"events.0.content.0.text: minimum string length is 1\"}}"
level=INFO msg="dispatched tool" tool=bash is_error=false posted=false ← result never posted
Then, repeatedly: Cannot send events to archived session: sesn_… (re-claimed ~every 10 min).
Root cause (client-side): When a tool result is empty, the client builds a text content block of length 0, violating the API contract (length ≥ 1). It then (a) treats the permanent 4xx as non-terminal ("not retrying" but continues half-acked), and (b) treats an archived session as non-terminal in the poller, re-claiming dead work forever.
Recovery / impact: A single empty tool output kills a session unrecoverably. There is no CLI surface to drain/cancel the poisoned work (work stats only); recovery requires a console-side queue drain or environment recreation. Health is misleading — process and systemctl stay green; only log progress reveals the wedge.
Not workaround-able at the shell level (we tried): We attempted to force non-empty output via an EXIT trap, a DEBUG trap through BASH_ENV, and a wrapper binary. Each verified in isolation; each failed in the live worker — the worker's bash invocation either doesn't honor the injected config or strips the injected (whitespace/zero-width) marker before serialization, and the worker namespace isn't externally inspectable. This must be fixed in the client.
Related: Same root-cause class as anthropics/claude-code issue #24662 ("Session becomes permanently unrecoverable after empty text content block").
Any one of these fixes it (priority order):

Never emit an empty text block — substitute a non-empty sentinel (e.g. (no output)) or omit the empty content block.
Treat a permanent 4xx on a tool-result post as terminal for the session, rather than continuing half-acked.
Treat "archived session" as terminal in the work poller — never re-claim archived work.

What Should Happen?

Summary: In a self-hosted ant beta:worker poll session, any tool returning empty stdout permanently wedges the session. The worker POSTs a tool-result event with an empty text block; the API rejects it (400, events.0.content.0.text: minimum string length is 1); the client logs "not retrying" but leaves the turn unacknowledged; the session is archived server-side; subsequent posts fail with "Cannot send events to archived session"; and the poller re-claims the same archived work indefinitely. The process and systemctl status stay green throughout (liveness ≠ progress).
Severity: High for the self-hosted coding-agent use case. The trigger is not rare — it fires on any silently-succeeding command: mkdir, touch, cp, mv, rm, cd, export, chmod all emit no stdout on success. A coding agent hits these constantly; sessions wedge routinely. In our case it fired on a mkdir during the agent's preflight — its first action.
Build:

ant version 1.9.0
/usr/local/bin/ant — statically-linked x86-64 ELF, ~22 MB, build date 2026-05-19, BuildID[sha1]=c5009a84719a5148daadaf363df14692b659f4ca
Invocation: ant beta:worker poll --environment-id env_… --workdir … --unrestricted-paths

Cannot send events to archived session: sesn_… (re-claimed ~every 10 min).
Root cause (client-side): When a tool result is empty, the client builds a text content block of length 0, violating the API contract (length ≥ 1). It then (a) treats the permanent 4xx as non-terminal ("not retrying" but continues half-acked), and (b) treats an archived session as non-terminal in the poller, re-claiming dead work forever.
Recovery / impact: A single empty tool output kills a session unrecoverably. There is no CLI surface to drain/cancel the poisoned work (work stats only); recovery requires a console-side queue drain or environment recreation. Health is misleading — process and systemctl stay green; only log progress reveals the wedge.
Not workaround-able at the shell level (we tried): We attempted to force non-empty output via an EXIT trap, a DEBUG trap through BASH_ENV, and a wrapper binary. Each verified in isolation; each failed in the live worker — the worker's bash invocation either doesn't honor the injected config or strips the injected (whitespace/zero-width) marker before serialization, and the worker namespace isn't externally inspectable. This must be fixed in the client.
Related: Same root-cause class as anthropics/claude-code issue #24662 ("Session becomes permanently unrecoverable after empty text content block").

Any one of these fixes it (priority order):

Never emit an empty text block — substitute a non-empty sentinel (e.g. (no output)) or omit the empty content block.
Treat a permanent 4xx on a tool-result post as terminal for the session, rather than continuing half-acked.
Treat "archived session" as terminal in the work poller — never re-claim archived work.

Error Messages/Logs

level=ERROR msg="tool result send hit permanent 4xx; not retrying"
  tool_use_id=sevt_…
  error="POST \"https://api.anthropic.com/v1/sessions/sesn_…/events?beta=true\":
  400 Bad Request {\"error\":{\"type\":\"invalid_request_error\",
  \"message\":\"events.0.content.0.text: minimum string length is 1\"}}"
level=INFO msg="dispatched tool" tool=bash is_error=false posted=false   ← result never posted

Steps to Reproduce

Run a self-hosted worker session where any tool returns empty output (e.g. a bash mkdir). Reproduces reliably.

Claude Model

Opus

Is this a regression?

No, this never worked

Last Working Version

_No response_

Claude Code Version

v1.9.0

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

WSL (Windows Subsystem for Linux)

Additional Information

_No response_

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗