anomalyco / anomalyco/opencode

Invalid tool-call repair drops required tool field for unnamed calls

Open
#47,830 1 comment 0 reactions 1 assignee View on GitHub

@neriousy is already working on this.

Since Sep 7, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Invalid tool-call repair drops the required tool field for unnamed calls

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.