sipeed/picoclaw · error
write process hook %q message: %w
Error message
write process hook %q message: %w
What it means
Returned by ProcessHook.send when the actual write of a marshaled JSON-RPC message to the hook's stdin fails — classically a broken pipe (EPIPE) because the hook subprocess already exited and closed its read end. The write runs in a goroutine with a context-race against ctx cancellation, and this error means the write itself errored.
Source
Thrown at pkg/agent/hook_process.go:398
body = append(body, '\n')
ph.writeMu.Lock()
defer ph.writeMu.Unlock()
if ph.closed.Load() {
return fmt.Errorf("process hook %q is closed", ph.name)
}
done := make(chan error, 1)
go func() {
_, writeErr := ph.stdin.Write(body)
done <- writeErr
}()
select {
case err := <-done:
if err != nil {
return fmt.Errorf("write process hook %q message: %w", ph.name, err)
}
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func (ph *ProcessHook) readLoop(stdout io.Reader) {
scanner := bufio.NewScanner(stdout)
scanner.Buffer(make([]byte, 0, 64*1024), processHookReadBufferSize)
for scanner.Scan() {
var msg processHookRPCMessage
if err := json.Unmarshal(scanner.Bytes(), &msg); err != nil {
logger.WarnCF("hooks", "Failed to decode process hook message", map[string]any{
"hook": ph.name,
"error": err.Error(),
})View on GitHub (pinned to 49183d7e8d)
Solutions
- Find out why the hook exited: its stderr pipe and exit status carry the cause (panic, os.Exit, crash)
- Ensure the hook reads stdin continuously and never exits while mounted
- Recreate the hook with NewProcessHook once the exit cause is fixed — the old instance is unusable
Defensive patterns
Strategy: fallback
Type guard
func isHookWriteError(err error) bool {
return err != nil && strings.Contains(err.Error(), "write process hook")
} Try / catch
if err := ph.Notify(ctx, event); err != nil {
if isHookWriteError(err) {
// broken pipe: hook process is gone — fall back to no-hook behavior
log.Printf("hook unreachable (%v); continuing without it", err)
return nil
}
return err
} Prevention
- Supervise hook subprocesses and remount on unexpected exit
- Keep hooks reading stdin for their whole lifetime
- Don't treat a dead logging/observe hook as fatal to the main flow
When it happens
Trigger: Any send/notification to a hook whose process has terminated — the OS pipe is still open on the host side but the reader is gone, so Write returns EPIPE/EIO. Distinct from error 271, which fires before writing when the closed flag is already observed.
Common situations: Hook process crashed between calls; hook exited normally but the host didn't notice yet; hook that doesn't keep stdin open; sends racing Close().
Related errors
- process hook %q closed while waiting for %s
- process hook %q is closed
- process hook %q %s failed: %s
- decode process hook %q %s result: %w
- builtin hook %q is not registered
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/16332ad109a8b4b7.
Report an issue: GitHub.