-
Notifications
You must be signed in to change notification settings - Fork 182
Description
Summary
When the agent calls branch then skip and hits the conversation depth limit (3/5), the turn ends with no text output. This empty assistant message gets persisted to conversation history. Since the Anthropic API rejects empty text content blocks, every subsequent message fails permanently with a 400 error. The bot goes silent with no user-facing error and no self-recovery.
100% reproducible on a completely fresh install (empty data dir, fresh config).
Steps to reproduce
- Fresh install:
ghcr.io/spacedriveapp/spacebot:latest(v0.1.15), Telegram channel,anthropic/claude-opus-4-6for all routing - Send any first message — bot replies successfully
- Send a message that triggers a
branchfor memory recall (e.g. "how many memories do you have?") - Bot goes silent. All further messages fail.
Log trace
Message 1 — works fine:
09:55:53 handle_message message_id=28
09:55:57 reply tool called content_len=272
Message 2 — triggers the bug:
09:56:12 handle_message message_id=30
09:56:16 executed tool: branch (memory recall) ← depth 2/5
09:56:20 executed tool: skip ("Waiting for branch") ← depth 3/5
09:56:22 Depth reached: 3/5 ← turn ends, no text produced
Branch completes, retrigger fires — already broken:
09:56:28 branch.run Depth reached: 2/50 ← branch finishes
09:56:28 handle_message (retrigger)
09:56:29 ERROR: Anthropic API error (400): messages: text content blocks must be non-empty
All subsequent messages — permanently broken:
09:56:30 handle_message message_id=31
09:56:31 ERROR: Anthropic API error (400): messages: text content blocks must be non-empty
Root cause analysis
We traced the full code path through spacebot and rig-core 0.30.0:
Bug 1: Empty/whitespace text blocks poison conversation history
Path: rig-core/src/agent/prompt_request/mod.rs line ~436 → channel.rs:apply_history_after_turn
- LLM returns response with tool calls (branch + skip) at depth 2/5
- Tools execute, results pushed to history as User message
- Loop continues, depth becomes 3/5
- LLM returns a response with no tool calls — rig pushes
Message::Assistant { content: [Text("")] }to history - "Depth reached: 3/5" fires, rig returns
MaxTurnsError apply_history_after_turn(channel.rs line ~2175) writes history back onMaxTurnsErrorwithout sanitizing- History now contains an assistant message with empty text content block
- Anthropic provider serializes via
TryFrom<message::AssistantContent> for Content(anthropic/completion.rs ~line 429) which passes empty string through with no filtering - Next API call → Anthropic rejects:
"text content blocks must be non-empty"
Note: Anthropic also rejects whitespace-only text (" ", "\n", etc.) with: "text content blocks must contain non-whitespace text". Both empty and whitespace-only must be filtered.
Fix location: apply_history_after_turn in src/agent/channel.rs — filter out AssistantContent::Text entries where text.trim().is_empty() before writing history back. Drop assistant messages that become entirely empty after filtering. This is ~15 lines of code.
Bug 2: Thinking-only API responses cause hard errors
Path: src/llm/model.rs:parse_anthropic_response (~line 1172)
When Anthropic returns a response containing only thinking blocks (no text, no tool calls), parse_anthropic_response discards all thinking blocks, leaving assistant_content empty. It then fails with CompletionError::ResponseError("empty response from Anthropic") and retries 3 times — all fail identically.
This happens after skip — the model has nothing to say, so it returns only a thinking block.
Fix location: parse_anthropic_response in src/llm/model.rs — when all content blocks are thinking/unknown (no text or tool calls), synthesize a valid placeholder response instead of erroring. The placeholder must then be stripped by the history sanitizer in Bug 1's fix to avoid poisoning.
Bug 3: Retrigger turns don't produce a reply
Even with bugs 1 and 2 fixed, the retrigger mechanism doesn't reliably deliver replies:
- Branch completes → results incorporated → debounced retrigger fires
- New turn starts at depth 1/5
- Model returns text without calling the
replytool → rig terminates the turn ("Depth reached: 1/5" on first depth) - User never gets a response
The model doesn't understand it needs to call reply on retrigger turns. The text it returns goes nowhere — spacebot only sends messages to Telegram via the reply tool.
Fix: Either auto-send text returned on retrigger turns when no reply was called, or make the retrigger prompt explicitly instruct the model to call reply.
Expected behavior
- Don't persist assistant messages with empty/whitespace-only text content to conversation history
- Handle thinking-only API responses gracefully instead of erroring
- Retrigger turns should reliably deliver a reply to the user
Environment
- Version: 0.1.15
- Image:
ghcr.io/spacedriveapp/spacebot:latest - Platform: x86_64, Docker, Linux
- LLM provider: Anthropic (
claude-opus-4-6) - Channel: Telegram