{"record":{"id":"608225877889ab08","repo":"can1357/oh-my-pi","slug":"unhandled-action-cmd-action","errorCode":null,"errorMessage":"Unhandled action ${cmd.action}","messagePattern":"Unhandled action (.+?)","errorType":"console","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/cli/ps-cli.ts","lineNumber":202,"sourceCode":"\t\t\tcase \"logs\":\n\t\t\t\tawait runLogs(cmd, client, name);\n\t\t\t\treturn;\n\t\t\tcase \"stop\":\n\t\t\tcase \"kill\": {\n\t\t\t\tconst timeoutMs = cmd.action === \"kill\" ? KILL_GRACE_MS : Math.round((cmd.flags.timeout ?? 5) * 1000);\n\t\t\t\tconst result = await client.request({ op: \"stop\", name, timeoutMs });\n\t\t\t\tif (result.op !== \"stop\") throw new Error(`Unexpected broker response ${result.op}`);\n\t\t\t\tprintDaemonResult(cmd, cmd.action === \"kill\" ? \"Killed\" : \"Stopped\", result.daemon);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"restart\": {\n\t\t\t\tconst result = await client.request({ op: \"restart\", name });\n\t\t\t\tif (result.op !== \"restart\") throw new Error(`Unexpected broker response ${result.op}`);\n\t\t\t\tprintDaemonResult(cmd, \"Restarted\", result.daemon);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unhandled action ${cmd.action}`);\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(chalk.red(error instanceof Error ? error.message : String(error)));\n\t\tprocess.exitCode = 1;\n\t}\n}\n\nfunction printDaemonResult(cmd: PsCommandArgs, verb: string, daemon: DaemonSnapshot): void {\n\tif (cmd.flags.json) console.log(JSON.stringify(daemon, null, 2));\n\telse console.log(`${verb} ${daemonLabel(daemon)}`);\n}\n\nasync function runLogs(cmd: PsCommandArgs, client: DaemonBrokerClient, name: string): Promise<void> {\n\tconst lines = Math.max(1, Math.min(1_000, Math.floor(cmd.flags.lines ?? 100)));\n\t// Follow mode reads the full 1000-line window on every request so overlap\n\t// trimming sees a stable, sliding tail; the initial print is cut to `lines`.\n\tconst first = await client.request({\n\t\top: \"logs\",","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/cli/ps-cli.ts#L184-L220","documentation":"`runAction` switches on `cmd.action` and handles info/logs/stop/kill/restart; the `default` branch throws for any other action value reaching the function. It is an internal exhaustiveness guard: the public `PsAction` type also includes \"list\", which `runPsCommand` handles before calling `runAction`, so this means an unhandled action leaked through dispatch.","triggerScenarios":"Calling `runAction` (or `runPsCommand`) with `action` set to \"list\" or any value outside {info, logs, stop, kill, restart} — e.g. programmatic use of the CLI API, a new action added to `PsAction` without a switch case, or an RPC/plugin invoking the ps command dispatcher directly with an unmapped action.","commonSituations":"A developer adds a new action to the `PsAction` union and the `PsCommandArgs` parser but forgets to add a `case` in `runAction`; an external caller (script, RPC mode) passes `action: \"list\"` into `runPsCommand` expecting it to fall through.","solutions":["Check the action string you passed — only info/logs/stop/kill/restart are handled by runAction; route `list` through the list path.","If you added a new action, add a corresponding `case` in the switch in `runAction` (ps-cli.ts).","Validate/normalize the action argument at the command-parser boundary before constructing `PsCommandArgs`."],"exampleFix":"// before\nswitch (cmd.action) {\n  case \"info\": /* ... */\n  case \"restart\": /* ... */\n  default:\n    throw new Error(`Unhandled action ${cmd.action}`);\n}\n// after\nswitch (cmd.action) {\n  case \"info\": /* ... */\n  case \"restart\": /* ... */\n  case \"pause\": {\n    const result = await client.request({ op: \"pause\", name });\n    /* ... */\n    return;\n  }\n  default: {\n    const exhaustive: never = cmd.action;\n    throw new Error(`Unhandled action ${String(exhaustive)}`);\n  }\n}","handlingStrategy":"validation","validationCode":"const ACTIONS = [\"info\", \"logs\", \"stop\", \"kill\", \"restart\"] as const;\nif (!ACTIONS.includes(cmd.action)) {\n  throw new Error(`Action \"${cmd.action}\" is not valid here; use ${ACTIONS.join(\"/\")}`);\n}","typeGuard":"function isRunnableAction(a: PsAction): a is Exclude<PsAction, \"list\"> {\n  return a !== \"list\";\n}","tryCatchPattern":"try {\n  await runAction(cmd, name);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Unhandled action\")) {\n    console.error(chalk.red(`${cmd.action} is not supported; run \\`omp ps --help\\``));\n    process.exitCode = 1;\n    return;\n  }\n  throw err;\n}","preventionTips":["Validate the action at the argument-parser boundary before building PsCommandArgs.","Make the switch's default branch use a `never` exhaustiveness check so new union members fail at compile time.","Keep the public PsAction union and runAction cases in sync when adding actions."],"tags":["cli","dispatch","unhandled-case"],"backgroundTag":"unhandled-switch-case","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}