← Back to PRs

#23698: fix: sanitize tool call IDs in agent loop for Mistral strict9 format (#23595)

by echoVic open 2026-02-22 16:11 View on GitHub →
agents size: XS trusted-contributor
## Summary Fixes #23595 — Mistral tool calls fail with HTTP 400 because tool call IDs contain underscores and don't match the required `[a-zA-Z0-9]{9}` format. ## Root Cause The existing `sanitizeToolCallIdsForCloudCodeAssist` mechanism only ran on **historical messages** at attempt start via `sanitizeSessionHistory`. However, pi-agent-core's agent loop internally cycles through tool call → tool result without going through that sanitization path. This means tool call IDs generated by the model (or by OpenClaw) during the current turn were sent back to Mistral unsanitized. Additionally, `run.ts` generated client tool call IDs using `call_${Date.now()}` which contains underscores and has variable length — incompatible with Mistral's strict format. ## Changes 1. **Wrap `streamFn` for tool call ID sanitization** (`attempt.ts`) - Follows the same pattern as `dropThinkingBlocks` — wraps the stream function so every outbound request sees sanitized tool call IDs - Only activates when `transcriptPolicy.sanitizeToolCallIds` is true (Mistral, Google, Anthropic) - Uses the correct mode (`strict9` for Mistral, `strict` for others) 2. **Fix `pendingToolCalls` ID generation** (`run.ts`) - Replace `call_${Date.now()}` with `randomBytes(5).toString('hex').slice(0, 9)` — 9-char alphanumeric, compatible with all providers 3. **Add Mistral error pattern** (`errors.ts`) - Add `/tool call id was.*must be/i` to `ERROR_PATTERNS.format` so the error is correctly classified for retry/rotation ## Testing - `tool-call-id.test.ts` — 7/7 ✅ - `sanitize-session-history.test.ts` — 20/20 ✅ - `transcript-policy.test.ts` + `policy.test.ts` — 6/6 ✅ - `auth-profile-rotation.test.ts` — 11/11 ✅ - `isbillingerrormessage.test.ts` — 30/30 ✅ <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR fixes Mistral API failures caused by tool call IDs not matching the required `[a-zA-Z0-9]{9}` format. The fix wraps the stream function in the agent loop to sanitize tool call IDs on every outbound request, not just historical messages. It also replaces the underscore-containing `call_${Date.now()}` ID generation with `randomBytes(5).toString('hex').slice(0, 9)` to ensure 9-character alphanumeric IDs. The error pattern `/tool call id was.*must be/i` is added for proper retry/rotation classification. The implementation follows the existing `dropThinkingBlocks` wrapper pattern and only activates for providers with `transcriptPolicy.sanitizeToolCallIds` enabled. <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk - The fix is well-implemented and follows established patterns in the codebase. All tests pass (7/7 for tool-call-id.test.ts, 20/20 for sanitize-session-history.test.ts). The changes are narrowly scoped to fixing the specific Mistral tool call ID issue, with proper conditional activation and appropriate fallback behavior. - No files require special attention <sub>Last reviewed commit: 64193fc</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs