Skip to content

Tool argument descriptions lost in schema conversion #4357

Description

@arsham

Description

I noticed that when I create custom tools in the ./tool directory and use .describe() on the arguments, those descriptions aren't showing up in the prompts sent to the LLM. The agent doesn't seem to know what the parameters are for, which leads to incorrect usage. At the moment I am adding them to the description of the tool itself, which is not ideal.

After digging through the source code, I found the issue. There are two different methods being used to convert Zod schemas to JSON Schema:

  1. In server.ts (line 307) - uses zodToJsonSchema() from the zod-to-json-schema package ✅
  2. In prompt.ts (line 581) - uses z.toJSONSchema() from Zod's built-in method ❌

The problem is that z.toJSONSchema() doesn't properly extract the .describe() metadata from schema properties, so all those descriptions get dropped before reaching the LLM.

(Possible) Fix:
Change line 581 in session/prompt.ts from:

const schema = ProviderTransform.schema(input.providerID, input.modelID, z.toJSONSchema(item.parameters))

To:

const schema = ProviderTransform.schema(input.providerID, input.modelID, zodToJsonSchema(item.parameters))

And add the import at the top:

import { zodToJsonSchema } from "zod-to-json-schema"

This makes the schema conversion consistent with what's already being done in server.ts and ensures all tool descriptions are properly passed to the LLM.

OpenCode version

v1.0.65

Steps to reproduce

For example, I have a tool like this:

export default tool({
    description: "Return the length of a string",
    args: {
        text: z.string().describe("The text string to measure"),
    },
    // ...
});

I expected the LLM to see the description "The text string to measure" for the text parameter, but it's not getting that information.

Screenshot and/or share link

No response

Operating System

Arch Linux

Terminal

foot

Activity

  1. added
    opentuiThis relates to changes in v1.0, now that opencode uses opentui
    on Nov 16, 2025
  2. self-assigned this
    on Nov 16, 2025
  3. removed
    opentuiThis relates to changes in v1.0, now that opencode uses opentui
    on Nov 16, 2025
  4. rekram1-node commented on Nov 17, 2025

    @rekram1-node
    Contributor

    @arsham I can't replicate this with any tool.

    Are you sure it is being sent without a description?

    This is how I tested:

    {
      "$schema": "https://opencode.ai/config.json",
      "plugin": ["opencode-openai-codex-auth"],
      "provider": {
        "opencode": {
          "api": "https://webhook.site/3bd417e1-b682-44c5-b743-2e4be1508605"
        }
      }
    }
    

    U can do this with any provider ofc but it just gives the exact request sent to the provider

  5. arsham commented on Nov 18, 2025

    @arsham
    Author

    @rekram1-node thank you for getting back to me. Here is an example tool you could try:

    import { tool } from "@opencode-ai/plugin";
    import { z } from "zod";
    
    export default tool({
        description: "Return what Arsham wants",
        args: {
            text: z.string().describe("You can heabang to this Death Metal tune!"),
        },
        async execute(args: { text: string }): Promise<string> {
            return "Done!";
        },
    });

    If you ask the agent to give you how the tool is represented verbatim in its tool set, you will get something like this:

    Here is the verbatim representation of the arsham tool as it appears in my toolbox:
    
    ```json
    {
      "description": "Return what Arsham wants",
      "name": "arsham",
      "parameters": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "additionalProperties": false,
        "properties": {
          "text": {
            "type": "string"
          }
        },
        "required": ["text"],
        "type": "object"
      }
    }
    ```
    
    
    **Key elements**:
    - **name**: `arsham`
    - **description**: "Return what Arsham wants"
    - **parameters**: JSON Schema object with:
      - `text` (type: string, required)
      - No additional properties allowed
      - Uses JSON Schema draft 2020-12
    
    That's the complete, verbatim definition provided to me in the system prompt.

    I might be doing something stupid here though.

  6. rekram1-node commented on Nov 18, 2025

    @rekram1-node
    Contributor

    I can replicate now, fixing shortly

  7. rekram1-node commented on Nov 18, 2025

    @rekram1-node
    Contributor

    some weird zod bug going on...

  8. rekram1-node commented on Nov 18, 2025

    @rekram1-node
    Contributor

    If you import a file dynamically, vs define the tool as we do, the zod parameters change wtf

  9. arsham commented on Nov 18, 2025

    @arsham
    Author

    wtf indeed!

  10. rekram1-node commented on Nov 19, 2025

    @rekram1-node
    Contributor

    This one is pretty obscure but there is something weird going on here:
    https://github.com/sst/opencode/blob/48e4f2f45d54364a8123d603dbbca7bfe8fcc763/packages/opencode/src/tool/registry.ts#L59

    I will try to attack this when I get a chance to, have a few higher priority ones but this is ofc a big issue for custom tools since descriptions are prompts.

    I guess work around for now:

    Overload the tool top level description to describe the tool parameters too

  11. arsham commented on Nov 19, 2025

    @arsham
    Author

    Thank you. I would this this issue is super low priority.

  12. added a commit that references this issue on Feb 13, 2026
    197a676
  13. added a commit that references this issue on Mar 4, 2026
    9664e47
  14. ashleytowner commented on Mar 4, 2026

    @ashleytowner

    This one is pretty obscure but there is something weird going on here: https://github.com/sst/opencode/blob/48e4f2f45d54364a8123d603dbbca7bfe8fcc763/packages/opencode/src/tool/registry.ts#L59

    I will try to attack this when I get a chance to, have a few higher priority ones but this is ofc a big issue for custom tools since descriptions are prompts.

    I guess work around for now:

    Overload the tool top level description to describe the tool parameters too

    @rekram1-node I spoke about it a bit on my PR #15957 but I think the issue is that there's two different instances of zod being used (one instance in the tool code itself imported from @opencode-ai/plugin) and another instance within the opencode app (imported from zod). Metadata on the schema (like .description()) is stored in zod's runtime registry, which is different for each instance of zod.

    This means that when zod instance A creates the object with the schema fields, but zod instance B is the one that then wraps it in z.object() and then z.toJsonSchema(), the descripton fields get dropped because they're not in the registry.

  15. samiralibabic commented on Mar 18, 2026

    @samiralibabic

    This also affects plugins, like https://github.com/samiralibabic/opencode-rexd-target

    Agents always call tools with timeouts in seconds, while they should be using milliseconds, but that information is "lost in translation" and agents never see it.

  16. khimaros commented on Mar 29, 2026

    @khimaros
    Contributor

    this really limits the capability of plugins

  17. jbmml commented on Apr 3, 2026

    @jbmml

    I investigated this on the latest dev branch and the original bug appears to be fixed. z.toJSONSchema() (Zod v4) now correctly preserves .describe() metadata on all schema properties — I verified this directly with a test schema.

    The Zod v4 migration resolved this for the prompt path (session/prompt.ts line 440).

    However, I found a related issue: server/routes/experimental.ts line 87 still uses zodToJsonSchema() from zod-to-json-schema@3.24.5, which is incompatible with Zod v4 and produces incorrect output (returns {"type": "string"} for object schemas). I've filed #20806 for that.

  18. ashleytowner commented on Apr 9, 2026

    @ashleytowner

    @jbmml how did you confirm this? Because it is still broken for me.

  19. khimaros commented on Apr 9, 2026

    @khimaros
    Contributor

    same. Ashley's patch is still needed. i have been rebasing on origin/dev regularly here: #19916

  20. jbmml commented on Apr 9, 2026

    @jbmml

    @ashleytowner @khimaros — You're right, and I appreciate the pushback. My earlier comment was wrong.

    I verified that z.toJSONSchema() preserves .describe() metadata for schemas defined in the same Zod instance, and jumped to "this is fixed" without testing the cross-instance case that actually matters for plugins. The real problem — plugin tools loading their own Zod instance, so .describe() metadata lives in the plugin's z.globalRegistry and is invisible to the core's z.toJSONSchema() — is exactly what @ashleytowner identified in #15957 and what @khimaros has been maintaining in #19916.

    #19916's rehydrateZodMeta() in registry.ts is the correct fix at the correct location. The metadata needs to be rehydrated before it reaches any schema conversion path.

    My PR #20809 fixes a separate, narrower bug — experimental.ts still uses the Zod v4-incompatible zodToJsonSchema() from zod-to-json-schema. That's the HTTP API endpoint, not the LLM tool-calling path that affects plugin descriptions. Both are real bugs, but they're independent.

    Sorry for the noise. +1 to getting #19916 merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions