{"record":{"id":"ecb174441f421400","repo":"sipeed/picoclaw","slug":"create-process-hook-stdin-w","errorCode":null,"errorMessage":"create process hook stdin: %w","messagePattern":"create process hook stdin: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/agent/hook_process.go","lineNumber":117,"sourceCode":"\ntype processHookAfterToolResponse struct {\n\tprocessHookDecisionResponse\n\tResult *ToolResultHookResponse `json:\"result,omitempty\"`\n}\n\nfunc NewProcessHook(ctx context.Context, name string, opts ProcessHookOptions) (*ProcessHook, error) {\n\tif len(opts.Command) == 0 {\n\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,","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/agent/hook_process.go#L99-L135","documentation":"Returned by NewProcessHook when exec.Cmd.StdinPipe() fails while wiring up the hook subprocess's stdin for JSON-RPC. StdinPipe allocates an OS pipe and a file descriptor, so in practice it fails only when the process is out of file descriptors (EMFILE/ENFILE) or the runtime cannot create a pipe.","triggerScenarios":"Calling NewProcessHook when the host process has exhausted its fd limit — e.g. thousands of open files/sockets or many concurrently mounted process hooks. The wrapped error is the raw os error from the pipe creation.","commonSituations":"Long-running agent daemons that mount hooks per-session and leak pipes; running under a container/orchestrator with a low ulimit -n; heavy concurrency spawning many hook processes at once.","solutions":["Raise the file-descriptor limit for the host process (ulimit -n or the container's nofile limit)","Find and close leaked files, sockets, or hook processes (each ProcessHook holds stdin/stdout/stderr pipes until closed)","Reduce the number of simultaneously mounted process hooks, or reuse one hook across sessions"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isPipeCreationError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"create process hook stdin:\")\n}","tryCatchPattern":"ph, err := NewProcessHook(ctx, name, opts)\nif err != nil {\n    if isPipeCreationError(err) {\n        // fd exhaustion: free descriptors, then retry once with fewer live hooks\n        releaseIdleHooks()\n        ph, err = NewProcessHook(ctx, name, opts)\n    }\n    if err != nil {\n        return err\n    }\n}","preventionTips":["Monitor open-fd count in long-running daemons","Close ProcessHook instances as soon as they are unmounted","Cap concurrent mounted hooks below your ulimit headroom"],"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"}