← Back to PRs

#19094: Fix empty tool_call_id and function names in provider transcript payloads

by yxshee open 2026-02-17 11:01 View on GitHub →
docs agents size: M
## Summary Fixes malformed tool transcript entries that can generate provider payloads with empty `tool_call_id` / empty function names. This change adds a global malformed-entry pruning pass before request build and blocks malformed tool results from being persisted going forward. Issue: https://github.com/openclaw/openclaw/issues/19003 ## Root cause - `2026.2.15` removed OpenAI tool-id sanitization in `src/agents/transcript-policy.ts`. - OpenAI request sanitation still skipped pairing repair by policy, so malformed historical tool-result entries could survive into outbound payloads. - `installSessionToolResultGuard(...)` in `src/agents/session-tool-result-guard.ts` allowed persisting `toolResult` entries even when id extraction was empty/missing. - Strict providers reject payloads containing invalid tool messages (for example `role: "tool"` with empty `tool_call_id`). ## Fix - Added global malformed tool-entry pruning in `src/agents/session-transcript-repair.ts`: - New `repairMalformedToolEntries(...)` and `sanitizeMalformedToolEntries(...)` APIs. - Expanded tool-call block type recognition to include aliases: `tool_call`, `tool_use`, `function_call`. - Drops malformed `toolResult` entries with missing/blank ids. - Applied malformed pruning in `sanitizeSessionHistory(...)` (`src/agents/pi-embedded-runner/google.ts`) before provider-specific pairing repair. - Added warning logging in the sanitize path when malformed entries are dropped. - Tightened id parsing in `src/agents/tool-call-id.ts`: - trims/rejects whitespace-only IDs and names - keeps valid canonical ids unchanged (including OpenAI `call_x|fc_y` form) - Added persistence-time guard in `src/agents/session-tool-result-guard.ts`: - malformed tool results with missing/blank ids are not persisted. ## Tests ### Added/updated tests - `src/agents/session-transcript-repair.e2e.test.ts` - drops blank/missing-id tool results - drops alias tool calls with blank names - preserves canonical OpenAI ids (`call_123|fc_123`) - `src/agents/pi-embedded-runner.sanitize-session-history.test.ts` - OpenAI path prunes malformed tool results and malformed alias tool calls - `src/agents/session-tool-result-guard.e2e.test.ts` - malformed tool results are not persisted - pending tool calls remain flushable ### Verification commands - `pnpm install` ✅ - `pnpm check` ✅ - `pnpm test` ✅ (`620` files passed, `5136` tests passed) - `pnpm build` ✅ ## Notes - No CLI flags, config schema, or RPC schema changes. - OpenAI id rewriting remains disabled by policy; malformed entries are pruned without rewriting canonical ids. - Updated docs in `docs/reference/transcript-hygiene.md` to describe global malformed tool-result pruning behavior. <!-- greptile_comment --> <h3>Greptile Summary</h3> This PR adds a global malformed-entry pruning pass for tool transcript entries to prevent strict providers (e.g., OpenAI) from rejecting payloads with empty `tool_call_id` or blank function names. The fix operates at two levels: (1) a pre-request sanitization step via `repairMalformedToolEntries` in `sanitizeSessionHistory` that drops tool results with missing/blank IDs and tool calls with blank names (including snake_case alias types), and (2) a persistence-time guard in `installSessionToolResultGuard` that rejects malformed tool results before they are written to the session transcript. - `repairMalformedToolEntries` in `session-transcript-repair.ts` composes the existing `repairToolCallInputs` with new tool-result pruning, and expands type recognition to include `tool_call`, `tool_use`, `function_call` aliases - `asNonEmptyString` helper in `tool-call-id.ts` ensures consistent trim + reject-whitespace behavior across all ID extraction points (`extractToolCallsFromAssistant`, `extractToolResultId`, and ID rewrite functions) - The persistence guard in `session-tool-result-guard.ts` now early-returns for malformed tool results, keeping the pending tool call flushable - Warning logging added in `sanitizeSessionHistory` when malformed entries are dropped - Tests added at unit, integration, and e2e levels covering blank/missing IDs, alias types, and canonical OpenAI ID preservation <h3>Confidence Score: 4/5</h3> - This PR is safe to merge — it adds defensive pruning of invalid entries with consistent behavior across extraction and persistence layers. - Score of 4 reflects well-structured changes with good test coverage and consistent behavior. The trimming behavior change in `extractToolResultId` (now returns trimmed strings via `asNonEmptyString`) is correctly applied at all call sites. One minor doc inconsistency noted (the "malformed tool calls" docs section still references `sanitizeToolCallInputs` as the direct implementation in `sanitizeSessionHistory` even though code now uses `repairMalformedToolEntries`), which doesn't affect runtime behavior. The `sanitizeMalformedToolEntries` export is unused but follows existing module patterns. - No files require special attention — the logic changes are straightforward defensive pruning with consistent behavior across all paths. <sub>Last reviewed commit: 69d25e6</sub> <!-- greptile_other_comments_section --> <sub>(3/5) Reply to the agent's comments like "Can you suggest a fix for this @greptileai?" or ask follow-up questions!</sub> <!-- /greptile_comment -->

Most Similar PRs