can1357/oh-my-pi · error · ToolError

Internal daemon result ${result.op} is not tool-visible

Error message

Internal daemon result ${result.op} is not tool-visible

What it means

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.

Source

Thrown at packages/coding-agent/src/tools/hub/launch.ts:299

	for (const condition of daemon.readyPending ?? []) {
		if (condition === "log") {
			parts.push(ready?.log ? `log pattern /${ready.log}/ never matched` : "the log pattern never matched");
		} else {
			parts.push(
				ready?.port !== undefined
					? `port ${ready.port} on ${ready.host ?? "127.0.0.1"} never accepted connections`
					: "the port never accepted connections",
			);
		}
	}
	return parts;
}

function toolContent(result: DaemonRpcResult, params: LaunchParams): string {
	switch (result.op) {
		case "ping":
		case "shutdown":
			throw new ToolError(`Internal daemon result ${result.op} is not tool-visible`);
		case "start": {
			const daemon = result.daemon;
			const lines = [`${daemon.state === "failed" ? "Failed to launch" : "Started"} ${daemonLabel(daemon)}`];
			if (daemon.state === "failed" && daemon.exitReason) lines.push(`Reason: ${daemon.exitReason}`);
			if (daemon.readyMatch) lines.push(`Ready log matched: ${daemon.readyMatch}`);
			if (result.readyTimedOut) {
				const pending = readyPendingSummary(daemon, params.ready);
				const cause = pending.length > 0 ? `: ${pending.join("; ")}` : "";
				lines.push(
					`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.`,
				);
			} else if (params.ready && daemon.readyAt === undefined && TERMINAL_STATES[daemon.state]) {
				lines.push("Process exited before readiness was observed.");
			}
			return lines.join("\n");
		}
		case "list":
			return result.daemons.length

View on GitHub (pinned to 9690622007)

Solutions

  1. Don't invoke the launch tool with op 'ping' or 'shutdown' — they are internal daemon-management ops, not tool operations.
  2. Update operationFor/executeLaunch so internal ops are filtered before toolContent renders them.
  3. Extend toolContent's switch if a new internal op legitimately needs tool-visible output.
Defensive patterns

Strategy: try-catch

Validate before calling

const TOOL_VISIBLE_OPS = new Set(["start", "stop", "send", "log", "list", "status"]);
if (!TOOL_VISIBLE_OPS.has(params.op)) throw new Error(`op ${params.op} is not a tool-visible launch operation`);

Try / catch

try {
  const content = launchTool.run(launchParams);
} catch (err) {
  if (err instanceof ToolError && err.message.includes("is not tool-visible")) {
    // internal-only op (ping/shutdown) reached the tool path — fix routing, don't expose to users
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/ee90e6f5dd247983. Report an issue: GitHub.