CedarCopilot wants to merge 0 commits into staging from fix/headless-chat-empty-response-retry
Live on prod, no production signal yet
OTEL fallback found no prod spans matching runChatAgent / runImessageAgent / runSmsAgent since deploy.
Libra has verdicts on 0 of 1 tracked behaviors on prod; 1 is still being checked. Libra checks hourly for 3 days after each deploy.
Users on headless chat surfaces intermittently got "I did not generate a response." (e.g. SimCare's Sam DMing the Slack bot , 6Γ over ~2 weeks).
Traced the reported failure through Postgres β Axiom β CloudWatch β the Understudy gateway logs:
stop, content: "").thinking:disabled was forwarded unchanged, the raw upstream Anthropic response was genuinely empty, and upstream latency was ~44s on a clean HTTP 200.So it's not the maxSteps ceiling and not extended thinking (both ruled out , thinking is disabled on this path and was verified off). It's a rare, transient empty completion from claude-sonnet-5 after a tool-use turn on a large context.
On an empty final answer, retry the turn once before falling back , on the Slack, iMessage, and SMS entrypoints. The empty completion is transient, so a single retry almost always recovers a real answer. Emits a warn log so the empty-completion rate is queryable in prod.
operation-registry) is intentionally left without a retry , it's a write path with a benign fallback, so a blind retry could double-apply.The retry re-runs the tool loop, so a turn that performed a write before returning empty could double it. The observed case was read-only and the event is rare, so this ships the simple version; write-detection can be added later if the warn logs show it firing on write-heavy turns.
pnpm --filter @zero/server run types , clean (run on the identical change in-place).thinking:disabled is honored and the empty completion is upstream.π€ Generated with Claude Code
The PR retries Slack, iMessage, and SMS headless-chat generation once when the model returns blank final text.
The retry should not merge unchanged because successful tool activity from an empty first generation can be lost from persisted conversation history.
All three retry branches overwrite the first generation result before persistence, so any tool events produced by that run are absent from the stored history even though their side effects may already have completed.
Files Needing Attention: apps/server/src/mastra/routeHandlers/slack/slack-bot-chat.ts, apps/server/src/mastra/routeHandlers/imessage/imessage-bot-chat.ts, apps/server/src/mastra/routeHandlers/sms/sms-bot-chat.ts
| Filename | Overview |
|---|---|
| apps/server/src/mastra/routeHandlers/slack/slack-bot-chat.ts | Adds one retry for blank agent output, but replacing result omits first-attempt tool events from persisted Slack history. |
| apps/server/src/mastra/routeHandlers/imessage/imessage-bot-chat.ts | Adds the same empty-completion retry and first-result replacement to iMessage. |
| apps/server/src/mastra/routeHandlers/sms/sms-bot-chat.ts | Adds the same empty-completion retry and first-result replacement to SMS. |
Libra has not measured any production surfaces for this change yet.