Skip to content

question tool: custom answer option cannot be disabled because "custom" is missing from the Prompt schema #45230

Open

Description

@harshil-mistry

Description

The question tool renders a "Type your own answer" option, and the TUI honours a custom: false flag to hide it — but nothing can ever set that flag, because it is missing from the schema the tool accepts.

packages/schema/src/question.ts:

const base = {
  question: ...,
  header: ...,
  options: ...,
  multiple: ...,
}

export const Info = Schema.Struct({
  ...base,
  custom: Schema.Boolean.pipe(optional).annotate({
    description: "Allow typing a custom answer (default: true)",
  }),
}).annotate({ identifier: "QuestionV2.Info" })

export const Prompt = Schema.Struct(base).annotate({ identifier: "QuestionV2.Prompt" })

custom lives on Info. Both tools take Prompt:

  • packages/opencode/src/tool/question.ts:7 — questions: Schema.mutable(Schema.Array(Question.Prompt))
  • packages/core/src/tool/question.ts:26 — questions: Schema.Array(QuestionV2.Prompt)

So custom never reaches the JSON schema the model sees, and a custom: false the model emitted anyway would be dropped as an excess property. Grepping the repo, nothing assigns custom anywhere — the only reader is the renderer:

packages/tui/src/routes/session/question.tsx:38

const custom = createMemo(() => question()?.custom !== false)

Since the value is always undefined, custom !== false is always true, and the input is always shown. The Info.custom field, its description, and the !== false guard are all live code for a state that cannot occur.

The tool description compounds it. packages/opencode/src/tool/question.txt:

When custom is enabled (default), a "Type your own answer" option is added automatically; don't include "Other" or catch-all options

"(default)" tells the model this is a setting with a non-default alternative. There is no way to reach it.

Which way to fix it

Two options, and I picked the first:

  1. Move custom into base so it reaches Prompt and the capability the renderer already implements becomes usable. One-line change; Info is unchanged in shape since base is spread into it.
  2. Drop custom from Info, delete the !== false guard, and remove the sentence from both tool descriptions — i.e. admit the option does not exist.

(1) seemed right because the rendering side is complete and correct; only the input schema is missing the field. Happy to switch to (2) if the intent was for custom to be host-controlled rather than model-controlled.

Steps to reproduce

  1. Call the question tool with custom: false on a question.
  2. The "Type your own answer" input is still rendered.
  3. Inspect the tool's JSON schema — custom is not among the accepted properties.

Operating System

Windows 11 (platform independent)

Activity

  1. changed the title [-]question tool: custom answer option cannot be disabled — is missing from the Prompt schema[/-] [+]question tool: custom answer option cannot be disabled because "custom" is missing from the Prompt schema[/+] on Aug 26, 2026
  2. dnlrsls commented on Sep 12, 2026

    @dnlrsls

    Additional impact observed in OpenCode 1.18.30: strict closed single-select prompts require both multiple: false and custom: false to be exposed and honored by the tool schema/runtime.

    multiple is already available, but because custom is absent from Question.Prompt, callers cannot disable free-text answers. Safety-sensitive consumers therefore cannot represent a closed answer domain in the native question UI and must fall back or stop.

    #45236 appears to address the missing field. This note is only to record the closed-domain use case; I did not open a separate issue for it.

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