{"record":{"id":"f07d6949b40e8553","repo":"chatboxai/chatbox","slug":"command-is-required","errorCode":null,"errorMessage":"Command is required","messagePattern":"Command is required","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/main/skills/user-exec-runner.ts","lineNumber":66,"sourceCode":"const activeUserExecCommands = new Map<string, ActiveUserExecCommand>()\n\nfunction getUserExecKey(sessionId: string | undefined, toolCallId: string | undefined): string | null {\n  return toolCallId ? `${sessionId ?? ''}:${toolCallId}` : null\n}\n\nexport function cancelUserExecCommand(params: Pick<UserExecParams, 'sessionId' | 'toolCallId'>): { killed: boolean } {\n  const key = getUserExecKey(params.sessionId, params.toolCallId)\n  const active = key ? activeUserExecCommands.get(key) : undefined\n  if (!active) return { killed: false }\n  active.cancel()\n  return { killed: true }\n}\n\nexport async function executeUserExecCommand(params: UserExecParams): Promise<UserExecResult> {\n  const { command, cwd: requestedCwd, timeout, sessionId, toolCallId, approvalSource } = params\n\n  try {\n    if (!command || typeof command !== 'string') throw new Error('Command is required')\n\n    const homeDir = os.homedir()\n    const cwd = requestedCwd?.trim() || homeDir\n    const timeoutMs = timeout || 120_000\n    const maxOutputBytes = 1024 * 1024 // 1MB\n    const operationId = createOperationId()\n    const startedAt = Date.now()\n\n    log.info(\n      buildOperationStartLog({\n        operationId,\n        kind: 'user_exec',\n        sessionId,\n        toolCallId,\n        // Renderer approval metadata is audit-only. Missing values remain visible\n        // instead of silently looking like a known authorization path.\n        approvalSource: approvalSource ?? 'unknown',\n        cwd,","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/skills/user-exec-runner.ts#L48-L84","documentation":"Thrown by executeUserExecCommand() when the command field is missing, empty, or not a string. This is the entry-point validation for the user-exec tool — the function that runs an arbitrary shell command approved by the user. It guards against invoking a shell with an undefined/empty command before any process spawning or logging occurs.","triggerScenarios":"The LLM tool-call payload omits command, passes an empty string, or passes a non-string (number, object, null). executeUserExecCommand destructures command from params and immediately checks `!command || typeof command !== 'string'`.","commonSituations":"The model emits a tool call with command: null or command: '' because it reasoned about an empty action; a deserialization bug in the tool-call argument parser drops the field; a test fixture constructs UserExecParams without command. The approval flow (approvalSource) runs regardless, but the command itself must be a non-empty string.","solutions":["Validate the command field in the tool-call argument schema (zod/string) before reaching executeUserExecCommand.","If the model sends an empty command, treat it as a no-op tool result rather than an error.","Ensure the tool-call deserializer preserves command as a required string field."],"exampleFix":"// before\nif (!command || typeof command !== 'string') throw new Error('Command is required')\n\n// after — reject earlier with the tool-call schema so the runner never sees a bad payload\nconst UserExecArgsSchema = z.object({ command: z.string().min(1), cwd: z.string().optional(), timeout: z.number().optional() })\nconst parsed = UserExecArgsSchema.parse(params)\n// executeUserExecCommand then assumes a valid command","handlingStrategy":"validation","validationCode":"import { z } from 'zod'\nconst UserExecArgsSchema = z.object({\n  command: z.string().min(1),\n  cwd: z.string().optional(),\n  timeout: z.number().int().positive().max(600000).optional(),\n  sessionId: z.string().optional(),\n  toolCallId: z.string().optional(),\n})\nconst parsed = UserExecArgsSchema.parse(params)\n// pass parsed to executeUserExecCommand","typeGuard":"function isUserExecParams(p: unknown): p is { command: string } {\n  return typeof (p as any)?.command === 'string' && ((p as any).command as string).length > 0\n}","tryCatchPattern":"// executeUserExecCommand returns a UserExecResult envelope; catch for the validation path only.\ntry {\n  const result = await executeUserExecCommand(params)\n  if (!result.success) reportError(result.stderr)\n} catch (e) {\n  if (e instanceof Error && /Command is required/i.test(e.message)) {\n    showToast('A command is required')\n  } else throw e\n}","preventionTips":["Validate the tool-call arguments with a schema before they reach the runner.","Treat an empty command as a no-op tool result rather than a thrown error.","Ensure the tool-call deserializer marks command as required."],"tags":["validation","user-exec","tool-call","input-validation","process"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}