{"record":{"id":"cefe45fb1379af3d","repo":"chenhg5/cc-connect","slug":"stdin-pipe-w","errorCode":null,"errorMessage":"stdin pipe: %w","messagePattern":"stdin pipe: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agent/pi/session.go","lineNumber":196,"sourceCode":"\t\targs = append(args, \"--model\", s.model)\n\t}\n\tif s.thinking != \"\" {\n\t\targs = append(args, \"--thinking\", s.thinking)\n\t}\n\n\tslog.Debug(\"piSession: starting RPC\", \"cmd\", s.cmd, \"args\", args)\n\n\tcmd := exec.CommandContext(s.ctx, s.cmd, args...)\n\tcmd.Dir = s.workDir\n\tenv := os.Environ()\n\tif len(s.extraEnv) > 0 {\n\t\tenv = core.MergeEnv(env, s.extraEnv)\n\t}\n\tcmd.Env = env\n\n\tstdinPipe, err := cmd.StdinPipe()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"stdin pipe: %w\", err)\n\t}\n\ts.rpcStdin = stdinPipe\n\ts.rpcCmd = cmd\n\n\tstdout, err := cmd.StdoutPipe()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"stdout pipe: %w\", err)\n\t}\n\tcmd.Stderr = &s.stderrBuf\n\n\tprepareCmdForKill(cmd)\n\n\tif err := cmd.Start(); err != nil {\n\t\treturn fmt.Errorf(\"start: %w\", err)\n\t}\n\n\ts.wg.Add(1)\n\tgo s.readLoopRPC(stdout)","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/agent/pi/session.go#L178-L214","documentation":"startRPC builds the exec.Cmd for the pi RPC subprocess and requests an stdin pipe via cmd.StdinPipe(). If the OS returns an error creating the pipe, it is wrapped as 'stdin pipe: %w'. This is a very low-level failure — pipe allocation happens almost exclusively inside os/exec.","triggerScenarios":"startRPC (called from newPiSession) when cmd.StdinPipe() fails — practically only on OS resource exhaustion: file descriptor limit hit (too many open files/processes), or the os/exec package already reported the command started/waited state.","commonSituations":"Daemons leaking subprocesses/pipes until ulimit -n is exhausted; containers with tiny fd limits; runaway session creation loops leaving zombie pi processes holding descriptors.","solutions":["Check for leaked pi/zombie subprocesses from prior sessions and kill them; investigate why sessions aren't being Stop()'d.","Raise the file descriptor limit: `ulimit -n 4096` or set LimitNOFILE=4096 in the systemd unit.","Restart cc-connect to release leaked descriptors, then fix the leak that exhausted them.","Inspect the wrapped cause (%w) to confirm EMFILE/ENFILE and monitor open fds with `ls /proc/<pid>/fd | wc -l`."],"exampleFix":"// systemd unit, before\n[Service]\nExecStart=/usr/local/bin/cc-connect\n// after\n[Service]\nLimitNOFILE=8192\nExecStart=/usr/local/bin/cc-connect","handlingStrategy":"retry","validationCode":"var lim syscall.Rlimit\nif err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim); err == nil && lim.Cur < 1024 {\n    log.Printf(\"low fd limit (%d); raise before spawning many pi sessions\", lim.Cur)\n}\nnFds, _ := countOpenFds(os.Getpid())\nif nFds > int(float64(lim.Cur)*0.8) {\n    return fmt.Errorf(\"fd usage %d/%d too high to spawn pi safely\", nFds, lim.Cur)\n}","typeGuard":null,"tryCatchPattern":"sess, err := agent.StartSession(ctx, opts)\nif err != nil && strings.Contains(err.Error(), \"stdin pipe\") {\n    log.Printf(\"fd/pipe allocation failed; check ulimit -n and leaked pi processes, then retry\")\n    return fmt.Errorf(\"resource exhaustion starting pi: %w\", err)\n}","preventionTips":["Set LimitNOFILE (systemd) or ulimit -n generously for the daemon.","Stop() sessions promptly; audit for zombie pi processes holding pipes.","Monitor open fd counts and alert before exhaustion."],"tags":["go","process-spawn","file-descriptors","pipe"],"backgroundTag":"file-descriptor-exhaustion","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}