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

  1. Raise the file-descriptor limit for the host process (ulimit -n or the container's nofile limit)
  2. Find and close leaked files, sockets, or hook processes (each ProcessHook holds stdin/stdout/stderr pipes until closed)
  3. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/ecb174441f421400. Report an issue: GitHub.