{"record":{"id":"ee90e6f5dd247983","repo":"can1357/oh-my-pi","slug":"internal-daemon-result-result-op-is-not-tool-vi","errorCode":null,"errorMessage":"Internal daemon result ${result.op} is not tool-visible","messagePattern":"Internal daemon result (.+?) is not tool-visible","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/hub/launch.ts","lineNumber":299,"sourceCode":"\tfor (const condition of daemon.readyPending ?? []) {\n\t\tif (condition === \"log\") {\n\t\t\tparts.push(ready?.log ? `log pattern /${ready.log}/ never matched` : \"the log pattern never matched\");\n\t\t} else {\n\t\t\tparts.push(\n\t\t\t\tready?.port !== undefined\n\t\t\t\t\t? `port ${ready.port} on ${ready.host ?? \"127.0.0.1\"} never accepted connections`\n\t\t\t\t\t: \"the port never accepted connections\",\n\t\t\t);\n\t\t}\n\t}\n\treturn parts;\n}\n\nfunction toolContent(result: DaemonRpcResult, params: LaunchParams): string {\n\tswitch (result.op) {\n\t\tcase \"ping\":\n\t\tcase \"shutdown\":\n\t\t\tthrow new ToolError(`Internal daemon result ${result.op} is not tool-visible`);\n\t\tcase \"start\": {\n\t\t\tconst daemon = result.daemon;\n\t\t\tconst lines = [`${daemon.state === \"failed\" ? \"Failed to launch\" : \"Started\"} ${daemonLabel(daemon)}`];\n\t\t\tif (daemon.state === \"failed\" && daemon.exitReason) lines.push(`Reason: ${daemon.exitReason}`);\n\t\t\tif (daemon.readyMatch) lines.push(`Ready log matched: ${daemon.readyMatch}`);\n\t\t\tif (result.readyTimedOut) {\n\t\t\t\tconst pending = readyPendingSummary(daemon, params.ready);\n\t\t\t\tconst cause = pending.length > 0 ? `: ${pending.join(\"; \")}` : \"\";\n\t\t\t\tlines.push(\n\t\t\t\t\t`NOT ready — readiness timed out after ${params.ready?.timeout ?? 30}s${cause}. The process is still running (state: ${daemon.state}); follow its logs or stop it.`,\n\t\t\t\t);\n\t\t\t} else if (params.ready && daemon.readyAt === undefined && TERMINAL_STATES[daemon.state]) {\n\t\t\t\tlines.push(\"Process exited before readiness was observed.\");\n\t\t\t}\n\t\t\treturn lines.join(\"\\n\");\n\t\t}\n\t\tcase \"list\":\n\t\t\treturn result.daemons.length","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/hub/launch.ts#L281-L317","documentation":"toolContent() converts a daemon RPC result into user-visible tool output. The 'ping' and 'shutdown' ops are internal-only (they have no meaningful tool-facing content), so if such a result reaches toolContent the code throws ToolError 'Internal daemon result <op> is not tool-visible' — a programming/invariant error indicating an internal result leaked into the tool path.","triggerScenarios":"executeLaunch dispatches an operation whose DaemonRpcResult op is 'ping' or 'shutdown' and then attempts to render it as tool content — e.g. an internal keepalive/shutdown result is mistakenly returned by the launch execution path instead of being handled internally.","commonSituations":"Routing table regression where the shutdown op was exposed as a user-selectable launch op; future refactors adding ops to operationFor without updating toolContent's switch; custom builds of the hub plumbing returning raw RPC results.","solutions":["Don't invoke the launch tool with op 'ping' or 'shutdown' — they are internal daemon-management ops, not tool operations.","Update operationFor/executeLaunch so internal ops are filtered before toolContent renders them.","Extend toolContent's switch if a new internal op legitimately needs tool-visible output."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const TOOL_VISIBLE_OPS = new Set([\"start\", \"stop\", \"send\", \"log\", \"list\", \"status\"]);\nif (!TOOL_VISIBLE_OPS.has(params.op)) throw new Error(`op ${params.op} is not a tool-visible launch operation`);","typeGuard":null,"tryCatchPattern":"try {\n  const content = launchTool.run(launchParams);\n} catch (err) {\n  if (err instanceof ToolError && err.message.includes(\"is not tool-visible\")) {\n    // internal-only op (ping/shutdown) reached the tool path — fix routing, don't expose to users\n  } else throw err;\n}","preventionTips":["Never expose internal ops (ping/shutdown) through the launch tool's public op parameter.","Extend toolContent whenever new DaemonRpcResult ops are added.","Add a unit test asserting every op produced by operationFor is handled by toolContent."],"tags":["internal","invariant-violation","rpc"],"backgroundTag":"internal-result-not-exposed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}