{"record":{"id":"57f81a63b504039b","repo":"sipeed/picoclaw","slug":"create-process-hook-stderr-w","errorCode":null,"errorMessage":"create process hook stderr: %w","messagePattern":"create process hook stderr: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/agent/hook_process.go","lineNumber":125,"sourceCode":"\t\treturn nil, fmt.Errorf(\"process hook command is required\")\n\t}\n\n\tcmd := exec.Command(opts.Command[0], opts.Command[1:]...)\n\tcmd.Dir = opts.Dir\n\tif len(opts.Env) > 0 {\n\t\tcmd.Env = append(os.Environ(), opts.Env...)\n\t}\n\tstdin, err := cmd.StdinPipe()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create process hook stdin: %w\", err)\n\t}\n\tstdout, err := cmd.StdoutPipe()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create process hook stdout: %w\", err)\n\t}\n\tstderr, err := cmd.StderrPipe()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create process hook stderr: %w\", err)\n\t}\n\t// Route hook subprocess startup through the shared isolation entry point so\n\t// process hooks inherit the same isolation behavior as other child processes.\n\tif err := isolation.Start(cmd); err != nil {\n\t\treturn nil, fmt.Errorf(\"start process hook: %w\", err)\n\t}\n\n\tph := &ProcessHook{\n\t\tname:         name,\n\t\topts:         opts,\n\t\tcmd:          cmd,\n\t\tstdin:        stdin,\n\t\tobserveKinds: newProcessHookObserveKinds(opts.ObserveKinds),\n\t\tpending:      make(map[uint64]chan processHookRPCMessage),\n\t\tdone:         make(chan struct{}),\n\t}\n\n\tgo ph.readLoop(stdout)","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/agent/hook_process.go#L107-L143","documentation":"Returned by NewProcessHook when exec.Cmd.StderrPipe() fails while creating the pipe that captures the hook subprocess's stderr. It is the third pipe allocated for the hook; failure means the OS refused the pipe creation, typically fd exhaustion.","triggerScenarios":"NewProcessHook gets through stdin and stdout pipe creation but the stderr pipe allocation fails because the fd table is full (EMFILE). The wrapped error is the raw os error.","commonSituations":"Processes near their fd ceiling — long-running agents with many live hooks, sessions, or connections; restrictive container fd limits.","solutions":["Increase the open-file limit for the host process","Free file descriptors: close idle hooks, connections, or files before mounting another process hook","Consolidate hooks so fewer subprocess pipes are needed simultaneously"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isStderrPipeError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"create process hook stderr:\")\n}","tryCatchPattern":"ph, err := NewProcessHook(ctx, name, opts)\nif err != nil {\n    if isStderrPipeError(err) {\n        return fmt.Errorf(\"cannot allocate hook pipes (fd limit?): %w\", err)\n    }\n    return err\n}","preventionTips":["Budget ~3 fds per mounted hook plus the subprocess itself","Clean up dead hooks eagerly to release their pipes","Set container nofile well above peak hook concurrency"],"tags":["os","process","file-descriptors","pipes","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}