anomalyco / anomalyco/opencode
Invalid tool-call repair drops the required `tool` field for unnamed calls
@rekram1-node is already working on this.
Since Sep 7, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Summary
experimental_repairToolCall in packages/opencode/src/session/llm.ts only
repairs tool names when the difference is casing. For an unknown, empty, or
missing toolCall.toolName, the fallback redirects the call to the synthetic
invalid tool and serializes the original name into the required tool field.
At v1.18.25, the relevant code is:
return {
...failed.toolCall,
input: JSON.stringify({
tool: failed.toolCall.toolName,
error: failed.error.message,
}),
toolName: "invalid",
}
When the name is undefined, JSON.stringify omits the tool property. The
synthetic tool then fails its own schema with an error such as:
SchemaError(Missing key at ["tool"])
In some cases the raw error returned to the model also contains a long English
description and possibly the complete list of available tools. This is
particularly confusing for local models using the OpenAI-compatible adapter.
We observed it with gpt-oss:120b served by Ollama. After repeated malformed
calls, the model can stop using tools and produce a fabricated textual result
instead of retrying the operation.
Environment
- OpenCode
v1.18.25(cb7d8b2f5e44876ef98b661dc10590c915af3a9f) - Provider: Ollama through
@ai-sdk/openai-compatible - Model:
gpt-oss:120b - Runtime: Linux x64 native Bun binary
Suggested fix
Normalize the tool name before calling .toLowerCase() and always provide a
valid, short error payload to the synthetic invalid tool:
@@
-const lower = failed.toolCall.toolName.toLowerCase()
-if (lower !== failed.toolCall.toolName && prepared.tools[lower]) {
+const toolName = typeof failed.toolCall.toolName === "string" ? failed.toolCall.toolName : ""
+const lower = toolName.toLowerCase()
+if (lower !== toolName && prepared.tools[lower]) {
@@
+const name = toolName || "unknown"
+const errorMessage = toolName
+ ? `Tool "${toolName}" is not available. Use the exact name of an available tool and try again.`
+ : "The call did not include a tool name. Use the exact name of an available tool and try again."
+
return {
...failed.toolCall,
input: JSON.stringify({
- tool: failed.toolCall.toolName,
- error: failed.error.message,
+ tool: name,
+ error: errorMessage,
}),
toolName: "invalid",
}
The important behavior is that the fallback must always satisfy the synthetic
tool schema and should avoid exposing an unbounded provider/runtime error to a
local model. A more general repair or retry policy could be considered later,
but this small change prevents the secondary schema failure and gives the
model an actionable instruction to retry with an exact tool name.
Expected behavior
For an unknown or missing tool name, OpenCode should emit one compact tool
error, keep the session alive, and allow the model to retry. It should not
fail a second time because the error-repair payload itself is missing a
required field.
Reproduction
Use a local OpenAI-compatible model configured for tool calling and induce a
malformed tool call with no tool name. Observe that the current fallback
produces Missing key at ["tool"] from the synthetic invalid tool.
Related context
The application layer can block fabricated success messages when no successful
tool result exists, but it cannot prevent OpenCode from converting the malformed
call into invalid. Fixing the repair payload upstream would reduce the chance
that the model abandons the tool workflow in the first place.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.