{"record":{"id":"70621a9b596d7bfa","repo":"paperclipai/paperclip","slug":"the-duplex-channel-launch-command-needs-at-least-o","errorCode":null,"errorMessage":"The duplex channel launch command needs at least one argument.","messagePattern":"The duplex channel launch command needs at least one argument\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/daytona/src/duplex-command-stream.ts","lineNumber":134,"sourceCode":" *   1. `exec 2>'<diagnosticsPath>'` redirects the shell's stderr to the file. The\n *      later `exec` inherits it, so the gateway diagnostics land in the file and\n *      never on the stdout frame stream.\n *   2. `stty raw -echo` sets the terminal to raw mode with echo off, so the\n *      terminal neither echoes host input as data nor translates newlines.\n *   3. `exec '<program>' '<arg>'...` replaces the shell with the gateway, so the\n *      PTY runs the gateway directly and the gateway exit code becomes the PTY\n *      exit code.\n *\n * The wrapper quotes the diagnostics path and every command argument as a\n * single-quoted shell word. So a shell metacharacter in a path or an argument\n * stays literal text and cannot inject a shell command.\n */\nexport function buildDuplexChannelLaunchWrapper(\n  command: readonly string[],\n  diagnosticsPath: string,\n): string {\n  if (command.length === 0) {\n    throw new Error(\"The duplex channel launch command needs at least one argument.\");\n  }\n  const quotedCommand = command.map(shellQuote).join(\" \");\n  return (\n    `exec 2>${shellQuote(diagnosticsPath)}; stty raw -echo; ` +\n    `exec ${quotedCommand}${PTY_COMMAND_TERMINATOR}`\n  );\n}\n\n/**\n * Opens a Daytona duplex channel PTY session for `command` and returns it as a\n * {@link DuplexChannelSession}. The session allocates a real pseudo-terminal in\n * raw mode, streams the raw output, accepts host input, and stops the child.\n *\n * The function decodes the terminal bytes as a UTF-8 stream, so a multibyte\n * character that splits across two output chunks stays whole. It buffers the\n * output until the transport registers the listener, so no early chunk is lost.\n */\nexport async function openDaytonaDuplexChannelSession(","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/packages/plugins/sandbox-providers/daytona/src/duplex-command-stream.ts#L116-L152","documentation":"Thrown by buildDuplexChannelLaunchWrapper in the Daytona sandbox provider when the command array is empty. The wrapper builds an `exec <cmd>` shell line run on a raw PTY; an empty argv has nothing to exec, so it refuses to build the line instead of producing a shell that silently hangs the duplex channel.","triggerScenarios":"Calling buildDuplexChannelLaunchWrapper([], diagnosticsPath) directly, or the provider's onDuplexChannelOpen receiving params.command = [] because the gateway command template rendered to zero arguments (empty config string, failed interpolation).","commonSituations":"Provider configuration where the command is assembled from optional pieces that are all unset; a template like `${prefix} ${rest}` with both empty; tests passing an empty array; a plugin version mismatch where the host sends an empty command for an unsupported gateway spec.","solutions":["Ensure the duplex gateway command has at least one element before opening the channel (the node binary path plus script path).","Check the command template/interpolation that produced params.command for undefined or empty segments.","Validate command.length > 0 at the config boundary so the failure surfaces at configuration time, not channel-open time."],"exampleFix":"// before\nconst line = buildDuplexChannelLaunchWrapper([], diagPath);\n\n// after\nif (command.length === 0) throw new Error(\"gateway command is empty; check provider config\");\nconst line = buildDuplexChannelLaunchWrapper(command, diagPath);","handlingStrategy":"validation","validationCode":"function isNonEmptyCommand(command: readonly string[]): boolean {\n  return command.length > 0 && command.every((a) => typeof a === \"string\" && a.length > 0);\n}","typeGuard":"function isLaunchableCommand(command: unknown): command is string[] {\n  return Array.isArray(command) && command.length > 0 &&\n    command.every((a) => typeof a === \"string\" && a.trim().length > 0);\n}","tryCatchPattern":"try {\n  wrapper = buildDuplexChannelLaunchWrapper(command, diagnosticsPath);\n} catch (error) {\n  if (error instanceof Error && error.message.includes(\"at least one argument\")) {\n    throw new Error(\"duplex gateway command is empty; fix the provider command template\");\n  }\n  throw error;\n}","preventionTips":["Validate the command template at provider-config load time, not at channel-open time.","Fail config load when an interpolation yields an empty binary path.","Cover this with a unit test on the command builder using a populated argv."],"tags":["daytona","duplex-channel","pty","validation","command"],"backgroundTag":"invalid-command-arguments","analyzedSha":"a7e689b3c35347b529cb9f54c9b9a8575a3dcab6","analyzedAt":"2026-08-21T17:58:32.592Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}