sipeed/picoclaw · error

create process hook stderr: %w

Error message

create process hook stderr: %w

What it means

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.

Source

Thrown at pkg/agent/hook_process.go:125

		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,
		cmd:          cmd,
		stdin:        stdin,
		observeKinds: newProcessHookObserveKinds(opts.ObserveKinds),
		pending:      make(map[uint64]chan processHookRPCMessage),
		done:         make(chan struct{}),
	}

	go ph.readLoop(stdout)

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Increase the open-file limit for the host process
  2. Free file descriptors: close idle hooks, connections, or files before mounting another process hook
  3. Consolidate hooks so fewer subprocess pipes are needed simultaneously
Defensive patterns

Strategy: try-catch

Type guard

func isStderrPipeError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "create process hook stderr:")
}

Try / catch

ph, err := NewProcessHook(ctx, name, opts)
if err != nil {
    if isStderrPipeError(err) {
        return fmt.Errorf("cannot allocate hook pipes (fd limit?): %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: Processes near their fd ceiling — long-running agents with many live hooks, sessions, or connections; restrictive container fd limits.

Related errors


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