← Back to PRs

#4009: fix(agent): sanitize messages after orphan user repair

by drag88 open 2026-01-29 14:23 View on GitHub →
agents
## Summary When repairing orphaned trailing user messages, `buildSessionContext()` was called but messages bypassed the sanitization pipeline. This could cause `tool_result` blocks without matching `tool_use` blocks, leading to Anthropic API rejecting requests with "unexpected tool_use_id" errors. **The fix:** Apply the full sanitization pipeline (`sanitizeSessionHistory`, `validateGeminiTurns`, `validateAnthropicTurns`, `limitHistoryTurns`) to rebuilt messages, matching the behavior of the initial sanitization at lines 502-524. ## Test Plan - [x] Tested locally with WhatsApp bot that was experiencing this error - [x] Confirmed the error no longer occurs after the fix - [x] Linter passes (`npm run lint`) ## AI-Assisted PR 🤖 - [x] **AI-assisted:** This fix was developed with Claude (Opus 4.5) - [x] **Testing level:** Fully tested - reproduced the bug, applied fix, verified resolution - [x] **Understanding:** The fix ensures that when messages are rebuilt via `buildSessionContext()` after repairing orphaned user messages, they go through the same sanitization pipeline as the initial message processing, preventing tool_use/tool_result pairing mismatches ### Context The bug was discovered when a WhatsApp bot session had an aborted request that created a branch in the conversation tree. When linearized, a `toolResult` was included without its corresponding `tool_use` block, causing the Anthropic API to reject the request. <!-- greptile_comment --> <h2>Greptile Overview</h2> <h3>Greptile Summary</h3> This change fixes a subtle session-repair edge case in `runEmbeddedAttempt()` where rebuilding session context after repairing an orphaned trailing user message would bypass the normal transcript sanitization/validation pipeline. The new code runs rebuilt messages through `sanitizeSessionHistory()`, optional turn validators (`validateGeminiTurns` / `validateAnthropicTurns`), and `limitHistoryTurns()` before calling `activeSession.agent.replaceMessages()`, aligning behavior with the initial session sanitization done earlier in the function. Overall this should prevent malformed tool_use/tool_result pairings (e.g., orphaned `tool_result` blocks) from reaching provider APIs like Anthropic and causing request rejections ("unexpected tool_use_id"). <h3>Confidence Score: 4/5</h3> - This PR looks safe to merge and addresses a real failure mode, with low behavioral risk outside the repair path. - The change is localized and mirrors an existing sanitization/validation pipeline already used earlier in the same function, which reduces the chance of introducing new transcript-format bugs. Main remaining concern is maintainability: the pipeline is duplicated in two places and could drift over time, but that’s not an immediate functional correctness issue. - src/agents/pi-embedded-runner/run/attempt.ts (keep the duplicated sanitization pipeline in sync with the earlier one) <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> <!-- /greptile_comment -->

Most Similar PRs