sipeed/picoclaw · error

builtin hook %q is not registered

Error message

builtin hook %q is not registered

What it means

At startup, every name enabled under hooks.builtins is looked up in an in-process registry populated by agent.RegisterBuiltinHook. picoclaw itself registers no builtins in the default build — they are an extension point for embeddings — so this error means config enables a builtin hook name that no RegisterBuiltinHook call in your binary provides (typo, renamed hook, or a missing side-effect import of the package that registers it).

Source

Thrown at pkg/agent/hook_mount.go:147

	}

	mounted := make([]string, 0)
	defer func() {
		if err != nil {
			for _, name := range mounted {
				al.UnmountHook(name)
			}
			return
		}
		al.hookRuntime.setMounted(mounted)
	}()

	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)

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. List the names your binary actually registers (grep your code for RegisterBuiltinHook calls) and fix the config name to match
  2. Import the package that calls RegisterBuiltinHook for that hook so its init() runs before startup
  3. Update picoclaw/embedding to the version that provides the hook you configured
  4. Set enabled=false for the unknown entry if the hook is not needed

Example fix

// before — config enables a name nothing registers
"hooks": { "enabled": true, "builtins": { "auditspell": { "enabled": true } } }

// after — name matches the registration
import picoclawhooks "yourcorp/picoclaw-extra-hooks" // registers "audit_spell"

"hooks": { "enabled": true, "builtins": { "audit_spell": { "enabled": true } } }
Defensive patterns

Strategy: validation

Validate before calling

registered := map[string]bool{
    // keep in sync with every RegisterBuiltinHook call linked into the binary
    "audit_spell": true,
}
for name, spec := range cfg.Hooks.Builtins {
    if spec.Enabled && !registered[name] {
        return fmt.Errorf("hooks.builtins.%s: no factory registered in this build", name)
    }
}

Prevention

When it happens

Trigger: hooks.enabled=true and hooks.builtins.<name>.enabled=true where <name> has no registered factory: misspelled name, a hook from a newer picoclaw version, or an extension package that registers the hook was never imported (so its init() never ran).

Common situations: Copying hooks config from docs of a different version; forks that dropped builtin hooks; forgetting that registration happens via package side effects, so dead-code elimination or a missing import silently drops the factory.

Related errors


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