{"record":{"id":"9e04b89333b6a84b","repo":"slopus/happy","slug":"failed-to-create-stdio-pipes","errorCode":null,"errorMessage":"Failed to create stdio pipes","messagePattern":"Failed to create stdio pipes","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/happy-cli/src/agent/acp/AcpBackend.ts","lineNumber":416,"sourceCode":"      } else {\n        this.process = spawn(this.options.command, args, {\n          cwd: this.options.cwd,\n          env: { ...process.env, ...this.options.env },\n          // Use 'pipe' for all stdio to capture output without printing to console\n          // stdout and stderr will be handled by our event listeners\n          stdio: ['pipe', 'pipe', 'pipe'],\n        });\n      }\n      \n      // Ensure stderr doesn't leak to console - redirect to logger only\n      // This prevents gemini CLI debug output from appearing in user's console\n      if (this.process.stderr) {\n        // stderr is already handled by the event listener below\n        // but we ensure it doesn't go to parent's stderr\n      }\n\n      if (!this.process.stdin || !this.process.stdout || !this.process.stderr) {\n        throw new Error('Failed to create stdio pipes');\n      }\n\n      let startupFailure: Error | null = null;\n      let startupFailureSettled = false;\n      let rejectStartupFailure: ((error: Error) => void) | null = null;\n      const startupFailurePromise = new Promise<never>((_, reject) => {\n        rejectStartupFailure = (error: Error) => {\n          if (startupFailureSettled) {\n            return;\n          }\n          startupFailureSettled = true;\n          startupFailure = error;\n          reject(error);\n        };\n      });\n      const signalStartupFailure = (error: Error) => {\n        rejectStartupFailure?.(error);\n      };","sourceCodeStart":398,"sourceCodeEnd":434,"githubUrl":"https://github.com/slopus/happy/blob/b824cd0a4681d41af631a8e422a813873e4455b0/packages/happy-cli/src/agent/acp/AcpBackend.ts#L398-L434","documentation":"startSession() spawns the agent as a child process and verifies that stdin, stdout, and stderr streams are all available on the returned ChildProcess before wiring ACP stdio transport. If any stream is missing, the process cannot speak the ACP JSON-RPC protocol over stdio, so the backend throws immediately.","triggerScenarios":"child_process spawn returned a process object with null stdin/stdout/stderr — typically when spawn failed (ENOENT on the command), the stdio option array didn't include 'pipe' for all three, or spawn options like detached/serialization misconfigured the pipes.","commonSituations":"Agent command not on PATH so spawn fails; custom `happy acp -- <command>` where the stdio config was altered; running in environments that restrict process spawning (sandboxes, minimal containers).","solutions":["Verify the agent command exists and is executable (`which <command>`) — a failed spawn yields null streams.","Ensure the backend spawns with stdio: ['pipe','pipe','pipe'] so all three streams are created.","Log the spawn error/exit event to find the root cause; handle the spawn 'error' event before this check."],"exampleFix":"// before\nspawn(cmd, args, { stdio: 'inherit' });\n// after\nspawn(cmd, args, { stdio: ['pipe', 'pipe', 'pipe'] });","handlingStrategy":"validation","validationCode":"await new Promise((resolve, reject) => {\n  const p = spawn(cmd, args, { stdio: ['pipe', 'pipe', 'pipe'] });\n  p.once('error', reject); // ENOENT etc. surfaces here\n  p.once('spawn', resolve);\n});","typeGuard":"function hasStdio(p: ChildProcess): p is ChildProcess & {\n  stdin: NodeJS.WriteStream; stdout: NodeJS.ReadStream; stderr: NodeJS.ReadStream;\n} {\n  return Boolean(p.stdin && p.stdout && p.stderr);\n}","tryCatchPattern":"try {\n  await backend.startSession();\n} catch (err) {\n  if (err instanceof Error && err.message === 'Failed to create stdio pipes') {\n    console.error(`Agent command failed to spawn: verify '${agentCommand}' exists and is executable`);\n  } else throw err;\n}","preventionTips":["Verify the agent command is on PATH before launching (`which <cmd>`).","Always spawn with stdio: ['pipe','pipe','pipe'] for ACP transport.","Listen for the child process 'error' event to catch spawn failures early.","Test agent startup in minimal containers/sandboxes where spawn may be restricted."],"tags":["stdio","spawn","child-process","acp"],"backgroundTag":"child-process-spawn-failed","analyzedSha":"b824cd0a4681d41af631a8e422a813873e4455b0","analyzedAt":"2026-08-31T23:12:36.205Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}