fix: auto-truncate question option labels exceeding 30 characters

When AI generates AskUserQuestion tool calls with option labels longer
than 30 characters, opencode validation rejects them with "too_big" error.

This fix adds a pre-tool-use hook that automatically truncates labels
to 30 characters (with "..." suffix) before the validation occurs.

Fixes the error:
"The question tool was called with invalid arguments: expected string
to have <=30 characters"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
yimingll
2026-01-22 10:23:41 +08:00
committed by justsisyphus
parent cf2320480f
commit 3a22c24cf4
3 changed files with 68 additions and 0 deletions

View File

@@ -30,3 +30,4 @@ export { createTaskResumeInfoHook } from "./task-resume-info";
export { createStartWorkHook } from "./start-work";
export { createAtlasHook } from "./atlas";
export { createDelegateTaskRetryHook } from "./delegate-task-retry";
export { createQuestionLabelTruncatorHook } from "./question-label-truncator";

View File

@@ -0,0 +1,63 @@
import type { Plugin } from "@opencode-ai/plugin";
const MAX_LABEL_LENGTH = 30;
interface QuestionOption {
label: string;
description?: string;
}
interface Question {
question: string;
header?: string;
options: QuestionOption[];
multiSelect?: boolean;
}
interface AskUserQuestionArgs {
questions: Question[];
}
function truncateLabel(label: string, maxLength: number = MAX_LABEL_LENGTH): string {
if (label.length <= maxLength) {
return label;
}
return label.substring(0, maxLength - 3) + "...";
}
function truncateQuestionLabels(args: AskUserQuestionArgs): AskUserQuestionArgs {
if (!args.questions || !Array.isArray(args.questions)) {
return args;
}
return {
...args,
questions: args.questions.map((question) => ({
...question,
options: question.options?.map((option) => ({
...option,
label: truncateLabel(option.label),
})) ?? [],
})),
};
}
export function createQuestionLabelTruncatorHook(): Pick<
Plugin,
"tool.execute.before"
> {
return {
"tool.execute.before": async (input, output) => {
const toolName = input.tool?.toLowerCase();
if (toolName === "askuserquestion" || toolName === "ask_user_question") {
const args = output.args as AskUserQuestionArgs | undefined;
if (args?.questions) {
const truncatedArgs = truncateQuestionLabels(args);
Object.assign(output.args as object, truncatedArgs);
}
}
},
};
}

View File

@@ -31,6 +31,7 @@ import {
createStartWorkHook,
createAtlasHook,
createPrometheusMdOnlyHook,
createQuestionLabelTruncatorHook,
} from "./hooks";
import {
contextCollector,
@@ -203,6 +204,8 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
? createPrometheusMdOnlyHook(ctx)
: null;
const questionLabelTruncator = createQuestionLabelTruncatorHook();
const taskResumeInfo = createTaskResumeInfoHook();
const backgroundManager = new BackgroundManager(ctx, pluginConfig.background_task);
@@ -484,6 +487,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
},
"tool.execute.before": async (input, output) => {
await questionLabelTruncator["tool.execute.before"]?.(input, output);
await claudeCodeHooks["tool.execute.before"](input, output);
await nonInteractiveEnv?.["tool.execute.before"](input, output);
await commentChecker?.["tool.execute.before"](input, output);