fix: normalize mangled tool names and IDs from OpenAI-compatible #39091
fix: normalize mangled tool names and IDs from OpenAI-compatible #39091gaharivatsa wants to merge 1 commit intoopenclaw:mainfrom
Conversation
…iders Some OpenAI-compatible providers (like Kimi via openai-completions) send tool calls with IDs like "functions.exec:0" which get corrupted to "functions exec:0" (space instead of dot), and tool names like "exec" become "functions exec". This causes "Tool functions exec not found" errors because the tool result cannot be matched to the original tool call. Changes: - Add normalizePrefixedToolName() to strip "functions." or "functions " prefixes from tool names in session-transcript-repair.ts - Add normalizeMangledToolCallId() to fix "functions " -> "functions." in tool call IDs in tool-call-id.ts - Update extractToolResultId() to normalize mangled IDs before matching - Update sanitizeToolCallIdsForCloudCodeAssist() to use normalized IDs as map keys, ensuring "functions.exec:0" and "functions exec:0" map to the same sanitized value - Add comprehensive tests for the normalization behavior Co-Authored-By: Claude Opus 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d4cd12c4a2
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // Normalize potentially mangled tool call IDs | ||
| return normalizeMangledToolCallId(toolCallId); |
There was a problem hiding this comment.
Keep toolResult ID normalization consistent with toolCalls
Normalizing toolCallId inside extractToolResultId breaks pairing when a provider already emits mangled IDs on both sides (for example assistant id: "functions exec:0" and toolResult toolCallId: "functions exec:0"). repairToolUseResultPairing compares this normalized result ID against raw assistant IDs from extractToolCallsFromAssistant, so toolCallIds.has(id) fails, the real tool result is treated as orphaned, and a synthetic missing-result error is inserted instead.
Useful? React with 👍 / 👎.
| // Normalize away any "functions." or "functions " prefix added by OpenAI-compatible providers | ||
| const normalizedName = normalizePrefixedToolName(trimmedName); | ||
| if (rawName !== normalizedName && normalizedName) { | ||
| const renamed = { ...(block as object), name: normalizedName } as typeof block; |
There was a problem hiding this comment.
Redact prefixed sessions_spawn calls before rewriting names
The new prefix normalization path rewrites names like functions.sessions_spawn to sessions_spawn in the non-redaction branch, but redaction only runs when the pre-normalized blockName already equals sessions_spawn. That means prefixed sessions_spawn tool calls now bypass sanitizeToolCallBlock and can persist attachments[].content in transcript history, which defeats the existing sessions_spawn redaction safeguard.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR adds normalization for mangled tool names and IDs from OpenAI-compatible providers. The core logic is sound and well-tested for assistant messages. However, a critical logic error in Confidence Score: 2/5
Last reviewed commit: d4cd12c |
| const finalRawName = | ||
| typeof rawToolName === "string" ? normalizePrefixedToolName(rawToolName.trim()) : rawToolName; | ||
| if (finalRawName === normalizedToolName) { | ||
| return message; |
There was a problem hiding this comment.
finalRawName and normalizedToolName are computed identically — both apply normalizePrefixedToolName to the same value (rawToolName.trim()). This makes the comparison on line 199 always true, so the function always returns the original message unchanged and never updates the toolName field on tool-result messages.
For example:
rawToolName = "functions.exec"normalizedToolName = normalizePrefixedToolName("functions.exec") = "exec"finalRawName = normalizePrefixedToolName("functions.exec".trim()) = "exec"← same computation"exec" === "exec"→ true → returns unchanged message (bug)
Result: Tool names like "functions.exec" are never normalized to "exec" in tool-result messages, even though the PR intends to strip this prefix.
The fix is to compare the original raw name against the normalized name:
| const finalRawName = | |
| typeof rawToolName === "string" ? normalizePrefixedToolName(rawToolName.trim()) : rawToolName; | |
| if (finalRawName === normalizedToolName) { | |
| return message; | |
| if (rawToolName === normalizedToolName) { | |
| return message; | |
| } | |
| return { ...message, toolName: normalizedToolName }; |
This reads: "if the raw name is already normalized, nothing needs to change". For "functions.exec" this evaluates to false, so the update is applied as intended.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/session-transcript-repair.ts
Line: 197-200
Comment:
`finalRawName` and `normalizedToolName` are computed identically — both apply `normalizePrefixedToolName` to the same value (`rawToolName.trim()`). This makes the comparison on line 199 always true, so the function always returns the original message unchanged and **never updates the `toolName` field** on tool-result messages.
For example:
- `rawToolName = "functions.exec"`
- `normalizedToolName = normalizePrefixedToolName("functions.exec") = "exec"`
- `finalRawName = normalizePrefixedToolName("functions.exec".trim()) = "exec"` ← same computation
- `"exec" === "exec"` → true → returns unchanged message (bug)
**Result:** Tool names like `"functions.exec"` are never normalized to `"exec"` in tool-result messages, even though the PR intends to strip this prefix.
The fix is to compare the **original** raw name against the normalized name:
```suggestion
if (rawToolName === normalizedToolName) {
return message;
}
return { ...message, toolName: normalizedToolName };
```
This reads: "if the raw name is already normalized, nothing needs to change". For `"functions.exec"` this evaluates to false, so the update is applied as intended.
How can I resolve this? If you propose a fix, please make it concise.…ble providers Add normalizeMangledToolCallId (functions -> functions.) and use in extractToolResultId and sanitizeToolCallIdsForCloudCodeAssist. Add normalizePrefixedToolName in session-transcript-repair for assistant and tool-result names. Fixes Tool functions exec not found matching. Fixes openclaw#39091
Some OpenAI-compatible providers (like Kimi via openai-completions) send tool calls with IDs like "functions.exec:0" which get corrupted to "functions exec:0" (space instead of dot), and tool names like "exec" become "functions exec".
This causes "Tool functions exec not found" errors because the tool result cannot be matched to the original tool call.
Changes:
Summary
Describe the problem and fix in 2–5 bullets:
Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
List user-visible changes (including defaults/config).
If none, write
None.Security Impact (required)
Yes/No)Yes/No)Yes/No)Yes/No)Yes/No)Yes, explain risk + mitigation:Repro + Verification
Environment
Steps
Expected
Actual
Evidence
Attach at least one:
Human Verification (required)
What you personally verified (not just CI), and how:
Compatibility / Migration
Yes/No)Yes/No)Yes/No)Failure Recovery (if this breaks)
Risks and Mitigations
List only real risks for this PR. Add/remove entries as needed. If none, write
None.