sipeed/picoclaw · error
create process hook stdin: %w
Error message
create process hook stdin: %w
What it means
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.
Source
Thrown at pkg/agent/hook_process.go:117
type processHookAfterToolResponse struct {
processHookDecisionResponse
Result *ToolResultHookResponse `json:"result,omitempty"`
}
func NewProcessHook(ctx context.Context, name string, opts ProcessHookOptions) (*ProcessHook, error) {
if len(opts.Command) == 0 {
return nil, fmt.Errorf("process hook command is required")
}
cmd := exec.Command(opts.Command[0], opts.Command[1:]...)
cmd.Dir = opts.Dir
if len(opts.Env) > 0 {
cmd.Env = append(os.Environ(), opts.Env...)
}
stdin, err := cmd.StdinPipe()
if err != nil {
return nil, fmt.Errorf("create process hook stdin: %w", err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, fmt.Errorf("create process hook stdout: %w", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, fmt.Errorf("create process hook stderr: %w", err)
}
// Route hook subprocess startup through the shared isolation entry point so
// process hooks inherit the same isolation behavior as other child processes.
if err := isolation.Start(cmd); err != nil {
return nil, fmt.Errorf("start process hook: %w", err)
}
ph := &ProcessHook{
name: name,
opts: opts,View on GitHub (pinned to 49183d7e8d)
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
Defensive patterns
Strategy: try-catch
Type guard
func isPipeCreationError(err error) bool {
return err != nil && strings.Contains(err.Error(), "create process hook stdin:")
} Try / catch
ph, err := NewProcessHook(ctx, name, opts)
if err != nil {
if isPipeCreationError(err) {
// fd exhaustion: free descriptors, then retry once with fewer live hooks
releaseIdleHooks()
ph, err = NewProcessHook(ctx, name, opts)
}
if err != nil {
return err
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- create process hook stdout: %w
- create process hook stderr: %w
- session not found
- session already completed
- failed to load MCP servers: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/ecb174441f421400.
Report an issue: GitHub.