sipeed/picoclaw · error
mount builtin hook %q: %w
Error message
mount builtin hook %q: %w
What it means
After a builtin hook builds, it is attached via AgentLoop.MountHook -> HookManager.Mount, which only fails when the hook manager is nil, the name is empty, or the hook value is nil (pkg/agent/hooks.go:274). In this config-driven path the name comes from the config map key and the hook was just built non-nil, so the realistic cause is al.hooks being uninitialized — a hand-constructed AgentLoop that skipped the normal constructor wiring.
Source
Thrown at pkg/agent/hook_mount.go:160
builtinNames := enabledBuiltinHookNames(al.cfg.Hooks.Builtins)
for _, name := range builtinNames {
spec := al.cfg.Hooks.Builtins[name]
factory, ok := lookupBuiltinHook(name)
if !ok {
return fmt.Errorf("builtin hook %q is not registered", name)
}
hook, factoryErr := factory(ctx, spec)
if factoryErr != nil {
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,View on GitHub (pinned to 49183d7e8d)
Solutions
- Construct the AgentLoop through the standard constructor so the hook manager is created
- If hooks are not wanted, set hooks.enabled=false rather than building a hook-less loop with hooks on in config
- In tests, either use the full constructor or disable hooks in the test config
- Check the wrapped message — 'hook manager is not initialized' confirms the wiring diagnosis
Defensive patterns
Strategy: try-catch
Validate before calling
if cfg.Hooks.Enabled && len(cfg.Hooks.Builtins) > 0 {
if al.HooksManager() == nil { // adapt to your embedding's accessor
return fmt.Errorf("hooks enabled in config but AgentLoop has no hook manager; use the standard constructor")
}
} Try / catch
if err := al.MountHook(reg); err != nil {
if strings.Contains(err.Error(), "hook manager is not initialized") {
// wiring bug: rebuild the loop with the standard constructor; not a runtime-transient error
return fmt.Errorf("loop constructed without hook manager: %w", err)
}
return err
} Prevention
- Never hand-construct AgentLoop structs; use the provided constructor which wires the HookManager
- In tests, disable hooks in config instead of stripping subsystems from the struct
- Treat any MountHook failure as a programming error — surface it, do not swallow it
When it happens
Trigger: hooks.enabled=true with builtins enabled on an AgentLoop built without a HookManager (custom construction in tests or forks bypassing NewAgentLoop), making MountHook return 'hook manager is not initialized' which gets wrapped here.
Common situations: Unit tests assembling AgentLoop structs literally; forks that build the loop with reflection or partial constructors; disabling subsystems during construction but leaving hooks on in config.
Related errors
- mount process hook %q: %w
- builtin hook %q is not registered
- build builtin hook %q: %w
- configure process hook %q: %w
- start process hook %q: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/342ad1101ae92f86.
Report an issue: GitHub.