openclaw/openclaw · error · Error

Pi terminal command requires duplex transport

Error message

Pi terminal command requires duplex transport

What it means

Thrown by resumePiSession (the node-host command handler for PI_TERMINAL_RESUME_COMMAND) when the optional io argument is undefined. The resume command spawns a PTY via runNodePtyCommand, which needs duplex stdin/stdout transport to forward bytes both ways; a request that arrives without an io channel cannot satisfy that contract.

Source

Thrown at extensions/acpx/src/pi-session-catalog-runtime.ts:560

    hostId: request.hostId,
    label: nodeLabel(node),
  };
}

export async function listPiSessions(paramsJSON?: string | null): Promise<string> {
  return JSON.stringify(await listLocalPiSessionPage(parseNodeParams(paramsJSON)));
}

export async function readPiSession(paramsJSON?: string | null): Promise<string> {
  return JSON.stringify(await readLocalPiTranscriptPage(parseNodeParams(paramsJSON)));
}

export async function resumePiSession(
  paramsJSON?: string | null,
  io?: OpenClawPluginNodeHostCommandIo,
): Promise<string> {
  if (!io) {
    throw new Error("Pi terminal command requires duplex transport");
  }
  const params = decodeNodePtyResumeParams(paramsJSON, validatePiThreadId);
  const record = await requireLocalPiSession(params.threadId);
  const resolution = resolveNodeHostExecutable("pi", {
    env: process.env,
    pathEnv: process.env.PATH ?? process.env.Path ?? "",
    strategy: "direct",
  });
  if (!resolution) {
    throw new Error("Pi CLI is unavailable");
  }
  return JSON.stringify(
    await runNodePtyCommand(
      {
        file: resolution.executable,
        args: ["--session", params.threadId],
        cwd: record.cwd,
        cols: params.cols,

View on GitHub (pinned to 01804a7531)

Solutions

  1. Only invoke PI_TERMINAL_RESUME_COMMAND through a node host transport that supplies duplex io.
  2. If calling resumePiSession directly in tests, pass a stub io object matching OpenClawPluginNodeHostCommandIo.
  3. Gate the resume command in the host manifest so it is only advertised when duplex transport is available.
  4. If your transport cannot do duplex, do not advertise the resume command.

Example fix

// before
await resumePiSession(paramsJSON); // io omitted
// after
await resumePiSession(paramsJSON, duplexIo);
Defensive patterns

Strategy: type-guard

Validate before calling

function hasDuplexIo(io: unknown): io is OpenClawPluginNodeHostCommandIo {
  return !!io && typeof io === "object" && typeof (io as any).read === "function" && typeof (io as any).write === "function";
}
if (!hasDuplexIo(io)) throw new TypeError("resumePiSession requires duplex io");

Type guard

function isOpenClawPluginNodeHostCommandIo(value: unknown): value is OpenClawPluginNodeHostCommandIo {
  return !!value && typeof value === "object" &&
    typeof (value as { read?: unknown }).read === "function" &&
    typeof (value as { write?: unknown }).write === "function";
}

Try / catch

if (!io) {
  // respond with a transport-level error rather than spawning the resume command
  return { ok: false, error: "duplex transport required" };
}

Prevention

When it happens

Trigger: A node host invoking the resume command over a one-shot request/response transport that does not provide OpenClawPluginNodeHostCommandIo. A test harness calling resumePiSession directly without passing io. A custom transport that forgot to wire duplex IO.

Common situations: The acpx plugin is invoked through a transport that only supports simple invoke (single request, single response). Misuse of the public node command handler in tests. A transport regression that stopped attaching io to duplex-eligible commands.

Related errors


AI-assisted analysis of openclaw/openclaw@01804a7531 (2026-08-12). Data as JSON: /api/errors/d2fb0176874c2333. Report an issue: GitHub.