sipeed/picoclaw · error

configure process hook %q: %w

Error message

configure process hook %q: %w

What it means

Every enabled entry in hooks.processes is validated by processHookOptionsFromConfig before anything launches. This wrapper means validation failed for that hook: transport is not 'stdio', command is empty, an observe entry is not a known event kind, an intercept entry is unsupported, or no hook mode is enabled at all. The unwrapped message says which constraint failed.

Source

Thrown at pkg/agent/hook_mount.go:170

			return fmt.Errorf("build builtin hook %q: %w", name, factoryErr)
		}
		if err := al.MountHook(HookRegistration{
			Name:     name,
			Priority: spec.Priority,
			Source:   HookSourceInProcess,
			Hook:     hook,
		}); err != nil {
			return fmt.Errorf("mount builtin hook %q: %w", name, err)
		}
		mounted = append(mounted, name)
	}

	processNames := enabledProcessHookNames(al.cfg.Hooks.Processes)
	for _, name := range processNames {
		spec := al.cfg.Hooks.Processes[name]
		opts, buildErr := processHookOptionsFromConfig(spec)
		if buildErr != nil {
			return fmt.Errorf("configure process hook %q: %w", name, buildErr)
		}

		processHook, buildErr := NewProcessHook(ctx, name, opts)
		if buildErr != nil {
			return fmt.Errorf("start process hook %q: %w", name, buildErr)
		}
		if err := al.MountHook(HookRegistration{
			Name:     name,
			Priority: spec.Priority,
			Source:   HookSourceProcess,
			Hook:     processHook,
		}); err != nil {
			_ = processHook.Close()
			return fmt.Errorf("mount process hook %q: %w", name, err)
		}
		mounted = append(mounted, name)
	}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Read the unwrapped inner message — it names the exact failed constraint
  2. Set transport to "stdio" or remove the field (it is the default)
  3. Provide command as a non-empty argv array
  4. Use valid observe kinds (runtime event names such as tool_exec_start, turn_end, or '*' for all)
  5. Enable at least one mode: observe non-empty or intercept containing before_llm/after_llm/before_tool/after_tool/approve_tool

Example fix

// before
"processes": { "audit": { "enabled": true, "transport": "grpc",
  "command": [], "intercept": ["before_llm"] } }

// after
"processes": { "audit": { "enabled": true,
  "command": ["/usr/local/bin/audit-hook"],
  "intercept": ["before_llm"] } }
Defensive patterns

Strategy: validation

Validate before calling

validIntercept := map[string]bool{"before_llm": true, "after_llm": true, "before_tool": true, "after_tool": true, "approve_tool": true}
for name, p := range cfg.Hooks.Processes {
    if !p.Enabled {
        continue
    }
    if p.Transport != "" && p.Transport != "stdio" {
        return fmt.Errorf("hooks.processes.%s: only stdio transport supported", name)
    }
    if len(p.Command) == 0 {
        return fmt.Errorf("hooks.processes.%s: command required", name)
    }
    hasMode := len(p.Observe) > 0 || len(p.Intercept) > 0
    for _, it := range p.Intercept {
        if !validIntercept[it] {
            return fmt.Errorf("hooks.processes.%s: bad intercept %q", name, it)
        }
    }
    if !hasMode {
        return fmt.Errorf("hooks.processes.%s: enable observe or intercept", name)
    }
}

Prevention

When it happens

Trigger: A hooks.processes.<name> entry with enabled=true and any of: transport set to something other than "stdio" (empty is fine), command missing/empty, observe listing invalid kinds, intercept outside {before_llm, after_llm, before_tool, after_tool, approve_tool}, or all of observe/intercept left empty ('no hook modes enabled').

Common situations: Assuming network transports (http/grpc) exist for hooks like they do for MCP; enabling a process hook before writing its command; event-kind typos like 'tool_start' instead of 'tool_exec_start'; forgetting that a process hook must observe or intercept something.

Related errors


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