[BUG] ant beta:worker v1.9.0 — empty tool output → 400 "minimum string length is 1" → permanently wedged session (archived-redelivery loop)
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_
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗