openclaw - 💡(How to fix) Fix Strict tool_call ID sanitization breaks Kimi K2.5 multi-turn tool calling [1 comments, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#62319Fetched 2026-04-08 03:06:05
View on GitHub
Comments
1
Participants
1
Timeline
1
Reactions
0
Participants
Timeline (top)
commented ×1

Root Cause

Root Cause

Code Example

import requests, json

# Sanitized IDs (how OpenClaw sends them)FAILS ~80%
messages = [
    {"role": "system", "content": "You are an assistant. " + "Context. " * 7000},
    {"role": "user", "content": "Read voice.md"},
    {"role": "assistant", "content": None, "tool_calls": [
        {"id": "functionsread0", "type": "function",  # ← sanitized ID
         "function": {"name": "read", "arguments": '{"file_path": "voice.md"}'}}
    ]},
    {"role": "tool", "tool_call_id": "functionsread0", "content": "Voice: be direct."},
    {"role": "user", "content": "Now read targets.md"},
]
# → finish_reason: "stop", no tool_calls (80% of the time)

# Native Kimi IDsPASSES 100%
# Same conversation but with "functions.read:0" instead of "functionsread0"
# → finish_reason: "tool_calls", structured tool_calls returned

---

const requiresOpenAiCompatibleToolIdSanitization = params.modelApi === "openai-completions" || ...
   const sanitizeToolCallIds = isGoogle || isMistral || isAnthropic || requiresOpenAiCompatibleToolIdSanitization;
   const toolCallIdMode = ... sanitizeToolCallIds ? "strict" : void 0;

---

kimi: {
       anthropicToolSchemaMode: "openai-functions",
       anthropicToolChoiceMode: "openai-string-modes",
       openAiPayloadNormalizationMode: "moonshot-thinking"
       // No transcriptToolCallIdMode!
   }

---

const alphanumericOnly = id.replace(/[^a-zA-Z0-9]/g, "");

---

kimi: {
    anthropicToolSchemaMode: "openai-functions",
    anthropicToolChoiceMode: "openai-string-modes",
    openAiPayloadNormalizationMode: "moonshot-thinking",
    transcriptToolCallIdMode: "default"  // Preserve native IDs
}
RAW_BUFFERClick to expand / collapse

Bug Description

OpenClaw's "strict" tool call ID sanitization ([^a-zA-Z0-9] removal) corrupts Kimi K2.5's native tool call IDs, causing multi-turn agentic conversations to fail after 2–3 tool-calling rounds.

Severity: Critical — all multi-turn agentic flows break for Kimi K2.5 via CanopyWave.

Reproduction

  1. Configure Kimi K2.5 as primary model via CanopyWave (openai-completions API)
  2. Send a task requiring multiple tool-calling rounds (e.g., reading several files then acting on results)
  3. Observe: first 1–2 turns produce tool calls normally. Subsequent turns generate thinking + text indicating intent to call tools, but stopReason: "stop" is returned instead of "toolUse".

Minimal API repro (streaming):

import requests, json

# Sanitized IDs (how OpenClaw sends them) → FAILS ~80%
messages = [
    {"role": "system", "content": "You are an assistant. " + "Context. " * 7000},
    {"role": "user", "content": "Read voice.md"},
    {"role": "assistant", "content": None, "tool_calls": [
        {"id": "functionsread0", "type": "function",  # ← sanitized ID
         "function": {"name": "read", "arguments": '{"file_path": "voice.md"}'}}
    ]},
    {"role": "tool", "tool_call_id": "functionsread0", "content": "Voice: be direct."},
    {"role": "user", "content": "Now read targets.md"},
]
# → finish_reason: "stop", no tool_calls (80% of the time)

# Native Kimi IDs → PASSES 100%
# Same conversation but with "functions.read:0" instead of "functionsread0"
# → finish_reason: "tool_calls", structured tool_calls returned

Root Cause

Code path

  1. Kimi K2.5 returns tool call IDs in native format: functions.<tool_name>:<index> (e.g., functions.read:0)

  2. resolveTranscriptPolicy() in pi-embedded-BYdcxQ5A.js:27254-27270 applies strict sanitization to all openai-completions providers that aren't OpenAI:

    const requiresOpenAiCompatibleToolIdSanitization = params.modelApi === "openai-completions" || ...
    const sanitizeToolCallIds = isGoogle || isMistral || isAnthropic || requiresOpenAiCompatibleToolIdSanitization;
    const toolCallIdMode = ... sanitizeToolCallIds ? "strict" : void 0;
  3. Kimi provider capabilities (pi-embedded-BYdcxQ5A.js:18482-18486) have no transcriptToolCallIdMode override:

    kimi: {
        anthropicToolSchemaMode: "openai-functions",
        anthropicToolChoiceMode: "openai-string-modes",
        openAiPayloadNormalizationMode: "moonshot-thinking"
        // No transcriptToolCallIdMode!
    }
  4. sanitizeToolCallId() in pi-embedded-helpers-CGU2Pfj9.js:1298-1311 strips all non-alphanumeric characters:

    const alphanumericOnly = id.replace(/[^a-zA-Z0-9]/g, "");

    functions.read:0functionsread0

  5. When this mangled ID is sent back in conversation history, Kimi's serving layer cannot match it to the original tool definition, causing finish_reason: "stop" instead of "tool_calls".

Test results

ID FormatPass Rate (5 trials, streaming, 66K system prompt)
functionsread0 (OpenClaw strict)1/5 (20%)
functions.read:0 (Kimi native)5/5 (100%)
call_abc123def456 (OpenAI style)5/5 (100%)

Proposed Fix

Add transcriptToolCallIdMode: "default" to Kimi/Moonshot provider capabilities:

kimi: {
    anthropicToolSchemaMode: "openai-functions",
    anthropicToolChoiceMode: "openai-string-modes",
    openAiPayloadNormalizationMode: "moonshot-thinking",
    transcriptToolCallIdMode: "default"  // Preserve native IDs
}

The same should apply to the moonshot entry.

Alternatively, a new sanitization mode that preserves . and : (common in Kimi/Moonshot IDs) while still stripping other special characters.

Environment

  • OpenClaw 2026.4.2 (commit d74a122)
  • macOS Darwin 25.3.0, Apple Silicon
  • Kimi K2.5 via CanopyWave (https://inference.canopywave.io/v1)
  • pi-ai library (bundled in node_modules/@mariozechner/pi-ai)

Additional Context

  • CanopyWave support independently confirmed that tool_call ID format matters for Kimi (they pointed to Kimi API docs)
  • The bug is nondeterministic (~80% failure rate with sanitized IDs), likely due to internal serving-layer fallback behavior
  • Single-turn tool calls are NOT affected (no conversation history to corrupt)
  • The openai style call_<UUID> format also works fine — only the stripped-alphanumeric format fails

extent analysis

TL;DR

Adding transcriptToolCallIdMode: "default" to Kimi/Moonshot provider capabilities is likely to fix the issue by preserving native tool call IDs.

Guidance

  • Verify that the transcriptToolCallIdMode override is correctly applied to the Kimi provider capabilities in the pi-embedded-BYdcxQ5A.js file.
  • Test the fix with the provided minimal API repro example to ensure that the finish_reason is now "tool_calls" instead of "stop".
  • Consider adding the same override to the moonshot entry to ensure consistency.
  • If the issue persists, investigate the sanitization mode and consider introducing a new mode that preserves . and : characters in tool call IDs.

Example

kimi: {
    anthropicToolSchemaMode: "openai-functions",
    anthropicToolChoiceMode: "openai-string-modes",
    openAiPayloadNormalizationMode: "moonshot-thinking",
    transcriptToolCallIdMode: "default"  // Preserve native IDs
}

Notes

The fix assumes that the transcriptToolCallIdMode override is correctly applied and that the Kimi provider capabilities are properly configured. Additional testing may be necessary to ensure that the fix works in all scenarios.

Recommendation

Apply the workaround by adding transcriptToolCallIdMode: "default" to the Kimi/Moonshot provider capabilities, as this is a targeted fix that addresses the root cause of the issue.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING