can1357/oh-my-pi · error

send requires data or signal

Error message

send requires data or signal

What it means

A send operation must carry something to deliver: either data (stdin/pty input) or a signal. DaemonBroker throws this fixed-message error when operation.data and operation.signal are both undefined, since there is nothing to do. It is a request-validation guard in #send.

Source

Thrown at packages/coding-agent/src/launch/broker.ts:1117

				`Daemon ${operation.name} generation ${boundGeneration} exited${exit}; ` +
					"the wait was rejected instead of continuing against a replacement generation",
			);
		}
		// A for:"ready" wait that woke on a terminal exit without ever observing
		// readiness is still "not ready" — surface it as timed out so callers and the
		// renderer don't chain work against a dead process.
		const timedOut = operation.for === "ready" && !pattern ? !readyObserved() : !woke;
		return { op: "wait", daemon: record.snapshot, matched, timedOut };
	}

	async #send(operation: Extract<DaemonOperation, { op: "send" }>): Promise<DaemonRpcResult> {
		const record = this.#record(operation.name);
		await this.#refreshDetached(record);
		if (terminalState(record.snapshot.state) || record.snapshot.state === "stopping") {
			throw new Error(`Daemon ${operation.name} is ${record.snapshot.state}`);
		}
		if (operation.data === undefined && operation.signal === undefined) {
			throw new Error("send requires data or signal");
		}
		if (operation.data !== undefined) {
			if (record.pty) record.pty.write(operation.data);
			else if (record.input) {
				record.input.write(operation.data);
				await record.input.flush();
			} else throw new Error(`Daemon ${operation.name} stdin is unavailable`);
		}
		if (operation.signal) {
			if (process.platform === "win32" && record.pty) {
				if (operation.signal === "SIGINT") record.pty.write("\u0003");
				else record.pty.kill();
			} else {
				const processRef = record.snapshot.pid === undefined ? null : Process.fromPid(record.snapshot.pid);
				if (!processRef) throw new Error(`Daemon ${operation.name} process is unavailable`);
				processRef.killTree(SIGNAL_NUMBER[operation.signal]);
			}
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Provide the data string you intended to write to the daemon's stdin/pty.
  2. Provide the signal you intended to deliver (e.g. "SIGINT").
  3. Guard the call site: only issue a send op when at least one of data/signal is defined.

Example fix

// before
const op = { op: "send", name: "repl" }; // data lost in refactor
// after
const op = { op: "send", name: "repl", data: "run()\n" };
Defensive patterns

Strategy: validation

Validate before calling

if (op.data === undefined && op.signal === undefined) {
  throw new Error("send op needs data or signal");
}

Type guard

function isDeliverableSend(op: { data?: string; signal?: string }): boolean {
  return op.data !== undefined || op.signal !== undefined;
}

Try / catch

try {
  await broker.send(op);
} catch (err) {
  if (err instanceof Error && err.message === "send requires data or signal") {
    // caller bug: reconstruct the op with a payload
  } else throw err;
}

Prevention

When it happens

Trigger: Calling send with an empty payload — e.g. { op: "send", name: "x" } with neither data nor signal, or data explicitly set to undefined after conditional construction.

Common situations: Code that builds the send op conditionally (`...(cond ? { data } : {})`) where cond was false; deserialized RPC requests that dropped empty-string fields; wiring bugs passing the wrong variable.

Understand the failure class

Background: Missing required parameter errors: what 'X is required' and 'the required X param is missing' mean, and how to fix them — this error's family across 27 libraries.

Related errors


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