Skip to content

fix: normalize mangled tool names and IDs from OpenAI-compatible #39091

Open
gaharivatsa wants to merge 1 commit intoopenclaw:mainfrom
gaharivatsa:main
Open

fix: normalize mangled tool names and IDs from OpenAI-compatible #39091
gaharivatsa wants to merge 1 commit intoopenclaw:mainfrom
gaharivatsa:main

Conversation

@gaharivatsa
Copy link
Copy Markdown

@gaharivatsa gaharivatsa commented Mar 7, 2026

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

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem:
  • Why it matters:
  • What changed:
  • What did NOT change (scope boundary):

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

  • Closes #
  • Related #

User-visible / Behavior Changes

List user-visible changes (including defaults/config).
If none, write None.

Security Impact (required)

  • New permissions/capabilities? (Yes/No)
  • Secrets/tokens handling changed? (Yes/No)
  • New/changed network calls? (Yes/No)
  • Command/tool execution surface changed? (Yes/No)
  • Data access scope changed? (Yes/No)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS:
  • Runtime/container:
  • Model/provider:
  • Integration/channel (if any):
  • Relevant config (redacted):

Steps

Expected

Actual

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
  • Edge cases checked:
  • What you did not verify:

Compatibility / Migration

  • Backward compatible? (Yes/No)
  • Config/env changes? (Yes/No)
  • Migration needed? (Yes/No)
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly:
  • Files/config to restore:
  • Known bad symptoms reviewers should watch for:

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk:
    • Mitigation:

…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]>
@openclaw-barnacle openclaw-barnacle bot added agents Agent runtime and tooling size: M labels Mar 7, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +90 to +91
// Normalize potentially mangled tool call IDs
return normalizeMangledToolCallId(toolCallId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +309 to +312
// 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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 7, 2026

Greptile Summary

This 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 normalizeToolResultName() prevents tool-result messages from being normalized. The function compares finalRawName with normalizedToolName — both computed by applying the same normalization to the same input, making the comparison always true. This causes the function to always return unchanged, leaving tool names like "functions.exec" un-normalized in tool-result messages. The fix is a one-line change: compare the original rawToolName instead of the double-normalized finalRawName.

Confidence Score: 2/5

  • The PR has a logic error that prevents tool-result message normalization, but the primary ID-matching fix still works. Requires minimal one-line fix before merge.
  • The core fix for tool call ID normalization and assistant message tool-name stripping is implemented correctly and well-tested. However, the normalizeToolResultName() function contains a tautological guard that makes it never update tool names on tool-result messages. This is a secondary correctness bug: while the immediate "Tool functions exec not found" error is still fixed (matching is done by ID), the tool toolName fields in responses remain un-normalized, inconsistent with the PR's stated intent to strip "functions." prefixes throughout. The fix is a simple one-line change, but the bug prevents the full normalization coverage promised by the PR.
  • src/agents/session-transcript-repair.ts (lines 197–200: normalizeToolResultName logic error)

Last reviewed commit: d4cd12c

Comment on lines +197 to 200
const finalRawName =
typeof rawToolName === "string" ? normalizePrefixedToolName(rawToolName.trim()) : rawToolName;
if (finalRawName === normalizedToolName) {
return message;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
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.

deepujain added a commit to deepujain/openclaw that referenced this pull request Mar 7, 2026
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant