can1357/oh-my-pi · error · Error

Unexpected response ${result.op}

Error message

Unexpected response ${result.op}

What it means

In `#act`, after a restart/stop request is sent, the TUI checks the reply op is either "restart" or "stop" (the request is verb-dependent). If the broker returns any other op — an error envelope, a foreign message type — the action cannot be confirmed and this error is shown in the status bar. It indicates CLI/broker protocol disagreement rather than a failed daemon operation.

Source

Thrown at packages/coding-agent/src/cli/ps-tui.ts:158

		this.#ui.requestRender();
	}

	// -- actions ---------------------------------------------------------------

	async #act(verb: "stop" | "kill" | "restart"): Promise<void> {
		const entry = this.#flat[this.#selected];
		if (!entry) return;
		const name = entry.row.snapshot.name;
		this.#setStatus(chalk.yellow(`${verb} ${name}…`));
		try {
			const client = await this.#client(entry.scope);
			if (!client) throw new Error("Scope is not addressable from this machine");
			const result = await client.request(
				verb === "restart"
					? { op: "restart", name }
					: { op: "stop", name, timeoutMs: verb === "kill" ? KILL_GRACE_MS : 5_000 },
			);
			if (result.op !== "restart" && result.op !== "stop") throw new Error(`Unexpected response ${result.op}`);
			this.#setStatus(
				chalk.green(
					`${verb === "restart" ? "Restarted" : verb === "kill" ? "Killed" : "Stopped"} ${daemonLabel(result.daemon)}`,
				),
			);
			void this.#refresh();
		} catch (error) {
			this.#setStatus(
				chalk.red(`${verb} ${name} failed: ${error instanceof Error ? error.message : String(error)}`),
			);
		}
	}

	async #openInfo(): Promise<void> {
		const entry = this.#flat[this.#selected];
		if (!entry) return;
		try {
			const client = await this.#client(entry.scope);

View on GitHub (pinned to 9690622007)

Solutions

  1. Retry the action after the table refreshes; if the daemon already exited the row will show a terminal state.
  2. Restart the broker so CLI and broker share one protocol version, then retry.
  3. Verify only one omp installation/broker owns the runtime socket; remove stale brokers.

Example fix

// before
if (result.op !== "restart" && result.op !== "stop") throw new Error(`Unexpected response ${result.op}`);
// after
if (result.op === "error") throw new Error(`Action rejected: ${result.message}`);
if (result.op !== "restart" && result.op !== "stop") throw new Error(`Unexpected response ${result.op}`);
Defensive patterns

Strategy: type-guard

Validate before calling

const listing = await client.request({ op: "list" });
if (listing.op === "list" && !listing.daemons.some(d => d.name === name)) {
  setStatus(`${name} is already gone`);
  return;
}

Type guard

function isActionResponse(r: { op: string }): r is { op: "restart" | "stop"; daemon: DaemonSnapshot } {
  return r.op === "restart" || r.op === "stop";
}

Try / catch

try {
  const result = await client.request(request);
  if (!isActionResponse(result)) throw new Error(`Unexpected response ${result.op}`);
  this.#setStatus(chalk.green(`${verb} ${daemonLabel(result.daemon)}`));
} catch (error) {
  this.#setStatus(chalk.red(`${verb} ${name} failed: ${error instanceof Error ? error.message : String(error)}`));
}

Prevention

When it happens

Trigger: Pressing s/x/r in the monitor where `client.request(...)` resolves with a response whose `op` is neither "restart" nor "stop" — e.g. an error response because the daemon vanished mid-action, or a broker running an incompatible protocol version.

Common situations: Killing a daemon that exits/is reaped at the same moment; a broker upgraded between table refresh and action; two omp versions installed with the older broker still owning the runtime socket.

Related errors


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