{"record":{"id":"d2fb0176874c2333","repo":"openclaw/openclaw","slug":"pi-terminal-command-requires-duplex-transport","errorCode":null,"errorMessage":"Pi terminal command requires duplex transport","messagePattern":"Pi terminal command requires duplex transport","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"extensions/acpx/src/pi-session-catalog-runtime.ts","lineNumber":560,"sourceCode":"    hostId: request.hostId,\n    label: nodeLabel(node),\n  };\n}\n\nexport async function listPiSessions(paramsJSON?: string | null): Promise<string> {\n  return JSON.stringify(await listLocalPiSessionPage(parseNodeParams(paramsJSON)));\n}\n\nexport async function readPiSession(paramsJSON?: string | null): Promise<string> {\n  return JSON.stringify(await readLocalPiTranscriptPage(parseNodeParams(paramsJSON)));\n}\n\nexport async function resumePiSession(\n  paramsJSON?: string | null,\n  io?: OpenClawPluginNodeHostCommandIo,\n): Promise<string> {\n  if (!io) {\n    throw new Error(\"Pi terminal command requires duplex transport\");\n  }\n  const params = decodeNodePtyResumeParams(paramsJSON, validatePiThreadId);\n  const record = await requireLocalPiSession(params.threadId);\n  const resolution = resolveNodeHostExecutable(\"pi\", {\n    env: process.env,\n    pathEnv: process.env.PATH ?? process.env.Path ?? \"\",\n    strategy: \"direct\",\n  });\n  if (!resolution) {\n    throw new Error(\"Pi CLI is unavailable\");\n  }\n  return JSON.stringify(\n    await runNodePtyCommand(\n      {\n        file: resolution.executable,\n        args: [\"--session\", params.threadId],\n        cwd: record.cwd,\n        cols: params.cols,","sourceCodeStart":542,"sourceCodeEnd":578,"githubUrl":"https://github.com/openclaw/openclaw/blob/01804a75319da4b69c9ab98ceaa30477e22b8c0b/extensions/acpx/src/pi-session-catalog-runtime.ts#L542-L578","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Only invoke PI_TERMINAL_RESUME_COMMAND through a node host transport that supplies duplex io.","If calling resumePiSession directly in tests, pass a stub io object matching OpenClawPluginNodeHostCommandIo.","Gate the resume command in the host manifest so it is only advertised when duplex transport is available.","If your transport cannot do duplex, do not advertise the resume command."],"exampleFix":"// before\nawait resumePiSession(paramsJSON); // io omitted\n// after\nawait resumePiSession(paramsJSON, duplexIo);","handlingStrategy":"type-guard","validationCode":"function hasDuplexIo(io: unknown): io is OpenClawPluginNodeHostCommandIo {\n  return !!io && typeof io === \"object\" && typeof (io as any).read === \"function\" && typeof (io as any).write === \"function\";\n}\nif (!hasDuplexIo(io)) throw new TypeError(\"resumePiSession requires duplex io\");","typeGuard":"function isOpenClawPluginNodeHostCommandIo(value: unknown): value is OpenClawPluginNodeHostCommandIo {\n  return !!value && typeof value === \"object\" &&\n    typeof (value as { read?: unknown }).read === \"function\" &&\n    typeof (value as { write?: unknown }).write === \"function\";\n}","tryCatchPattern":"if (!io) {\n  // respond with a transport-level error rather than spawning the resume command\n  return { ok: false, error: \"duplex transport required\" };\n}","preventionTips":["Only advertise PI_TERMINAL_RESUME_COMMAND on transports that supply duplex io.","In tests, always pass a stub io matching OpenClawPluginNodeHostCommandIo.","Gate resume behind a capability flag so non-duplex hosts never invoke it."],"tags":["pi-session-catalog","node-command","duplex-transport","terminal","acpx"],"backgroundTag":null,"analyzedSha":"01804a75319da4b69c9ab98ceaa30477e22b8c0b","analyzedAt":"2026-08-12T04:37:58.197Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}