## Summary
The Anthropic-compatible API endpoint:
`https://api.moonshot.cn/anthropic/v1/messages`
appears to reuse the same `tool_use.id` across separate tool calls in the same multi-turn conversation when using `kimi-k3`.
I can reproduce this directly with raw HTTP requests, without Claude Code, MCP, hooks, plugins, or any agent framework.
Example:
- First tool call: `tool_use.id = "pwd_0"`
- Second, separate tool call: `tool_use.id = "pwd_0"`
The two tool calls belong to two different assistant responses with different `message.id` values.
This appears incompatible with Anthropic tool-use semantics, where the `tool_use.id` is the unique identifier for a particular tool-use block and is later referenced by `tool_result.tool_use_id`.
This causes tool-call/tool-result pairing problems in clients such as Claude Code and can lead to repeated tool invocation loops.
---
## Environment
- Model: `kimi-k3`
- API platform: Moonshot/Kimi Open Platform
- Endpoint: `https://api.moonshot.cn/anthropic/v1/messages`
- Protocol: Anthropic-compatible Messages API
- OS: Windows 11
- Reproduced with:
- direct Python HTTP client
- Claude Code 2.1.229
The API key is an Open Platform API key, not a Kimi Code subscription key.
---
## Minimal direct API reproduction
The issue can be reproduced without Claude Code.
The test performs the following conversation:
1. Send a user request asking Kimi K3 to call a `pwd` tool.
2. Receive the first `tool_use`.
3. Return a valid `tool_result` referencing the first `tool_use.id`.
4. Ask the model to call the same tool again.
5. Inspect the `tool_use.id` in the second assistant response.
Tool definition:
```json
[
{
"name": "pwd",
"description": "Return the current working directory.",
"input_schema": {
"type": "object",
"properties": {},
"additionalProperties": false
}
}
]
The first request contains:
{
"role": "user",
"content": "You must call the pwd tool exactly once."
}
After receiving the first tool call, the next user message contains its tool result plus a request to invoke the tool again:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "<ID_FROM_ROUND_1>",
"content": "/direct-test/round-1"
},
{
"type": "text",
"text": "Now call the pwd tool exactly once again."
}
]
}
No tool_choice is specified because kimi-k3 with thinking enabled rejects forced/specified tool_choice.
Actual result
Direct HTTP reproduction result:
Moonshot Anthropic tool_use.id diagnostic
URL : https://api.moonshot.cn/anthropic/v1/messages
MODEL: kimi-k3
========== ROUND 1 ==========
message.id : chatcmpl-56f6998b-4b2a-9c84-ae0f-6361cb5f7061
model : kimi-k3
stop_reason: tool_use
tool_use.id: pwd_0
========== ROUND 2 ==========
message.id : chatcmpl-85e8bc5d-e1cf-93c8-87f6-b691b30289b5
model : kimi-k3
stop_reason: tool_use
tool_use.id: pwd_0
========== VERDICT ==========
ROUND 1 tool_use.id: 'pwd_0'
ROUND 2 tool_use.id: 'pwd_0'
DUPLICATE_TOOL_USE_ID = YES
So these are two distinct assistant responses:
chatcmpl-56f6998b-4b2a-9c84-ae0f-6361cb5f7061
chatcmpl-85e8bc5d-e1cf-93c8-87f6-b691b30289b5
but both independent tool calls receive:
pwd_0
Expected result
Each distinct tool-use block should receive a distinct identifier.
For example:
Round 1:
tool_use.id = pwd_<unique-id-1>
Round 2:
tool_use.id = pwd_<unique-id-2>
or any other unique identifier scheme.
The exact format is not important. The important property is that the identifier for one particular tool invocation should not collide with another tool invocation in the same conversation.
Anthropic documentation describes tool_use.id as:
A unique identifier for this particular tool use block.
The corresponding tool_result.tool_use_id is then used to associate the result with that specific tool call.
Impact on Claude Code
I originally discovered the issue while using Kimi K3 through Claude Code with:
ANTHROPIC_BASE_URL=https://api.moonshot.cn/anthropic
Claude Code environment:
Claude Code v2.1.229
Model: kimi-k3
Windows 11
A typical sequence is:
User request #1
-> Bash tool call
-> toolUseId = Bash_0
-> tool result
-> succeeds
User request #2
-> Bash tool call
-> toolUseId = Bash_0 again
-> tool result
-> Claude Code detects/repairs tool-result pairing
-> model behaves as if an empty user message was received
-> retries the same Bash tool
-> receives Bash_0 again
-> loop continues
The Claude Code debug log contains pairing-repair events such as:
ensureToolResultPairing:
repaired missing tool_result blocks
At the same time, Kimi’s reasoning starts reporting messages such as:
The user sent an empty message.
or:
The user has sent empty messages twice.
This can create a self-sustaining retry loop.
Transcript verification
I also inspected the Claude Code JSONL transcript.
Important findings:
-
the user did not actually send empty messages;
-
the persisted transcript did not contain true empty
role=usermessages; -
the Bash commands were actually executed;
-
their
tool_resultblocks were present and non-empty; -
tool_use/tool_resultblocks were persisted; -
the model nevertheless behaved as if it had received empty user turns.
This initially made the issue look like a Claude Code runtime problem.
However, the direct HTTP reproduction above removes Claude Code from the test completely and confirms that the Moonshot Anthropic-compatible endpoint itself returns duplicate tool_use.id values.
Why this appears to be an Anthropic compatibility issue
Anthropic’s tool-use protocol uses:
{
"type": "tool_use",
"id": "<unique-tool-use-id>",
"name": "...",
"input": {}
}
and later:
{
"type": "tool_result",
"tool_use_id": "<unique-tool-use-id>",
"content": "..."
}
Therefore the ID is the pairing key between the individual tool invocation and its corresponding result.
If multiple independent tool calls in the same conversation reuse:
pwd_0
or:
Bash_0
clients that use this ID for pairing can no longer uniquely identify the invocation associated with a result.
Anthropic’s documentation describes the ID as a unique identifier for the particular tool-use block.
Reproducibility
The direct API test is deterministic enough to reproduce the ID collision:
Round 1 -> pwd_0
Round 2 -> pwd_0
The Claude Code symptom is intermittent in when it first becomes visible, but once the pairing problem starts, repeated tool invocations can form a loop.
The problem has also been observed by more than one user using Kimi through Claude Code, although the direct HTTP reproduction above is the strongest evidence and does not depend on Claude Code.
Suspected root cause
My current hypothesis is that the Anthropic-compatible adapter generates tool IDs using something similar to:
<tool_name>_<index_within_current_response>
rather than generating an identifier that remains unique across distinct tool-use blocks in the conversation.
For example:
response 1:
pwd_0
response 2:
pwd_0
response 3:
pwd_0
If so, each response resets the tool-call index to zero, producing collisions across turns.
This is only a hypothesis about the implementation; the confirmed observation is simply that separate tool calls receive the same ID.
Suggested fix
Please ensure that every distinct tool_use block returned by the Anthropic-compatible endpoint receives a unique ID.
For example:
toolu_<UUID>
or:
pwd_<request-or-call-unique-id>
The specific naming scheme does not matter as long as independent tool calls cannot collide and subsequent tool_result.tool_use_id values can unambiguously refer to the intended tool call.
Related issue
Kimi-K3 issue #12 reports a repeated tool-call loop:
- K3 repeatedly retries the same tool call after the harness does not accept/use the previous result as expected.
The symptom is related, although I am not claiming it has the same root cause.
The direct API reproduction in this issue specifically demonstrates duplicate tool_use.id values from the Anthropic-compatible endpoint.
Additional evidence available
I can provide, if useful:
-
the complete minimal Python reproduction script;
-
Claude Code JSONL transcripts from a minimal reproduction;
-
Claude Code
--debug apilogs showing repeatedBash_0IDs and pairing repair; -
additional repeated direct-API runs;
-
an OpenAI-compatible endpoint A/B comparison using the same Open Platform API key and the same
kimi-k3model.
No API credentials are included in any of the evidence.