sipeed/picoclaw · error

build builtin hook %q: %w

Error message

build builtin hook %q: %w

What it means

The factory for an enabled builtin hook ran but rejected its spec: the hooks.builtins.<name>.config JSON blob failed that hook's own validation. The factory's error is wrapped with the hook name, so the inner message names the exact field or value the hook rejected. Initialization aborts and no further hooks mount.

Source

Thrown at pkg/agent/hook_mount.go:152

			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)
	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)

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Read the wrapped error after the hook name — it identifies the invalid field/value
  2. Check the hook provider's documentation for the expected config schema and correct the JSON
  3. Validate the config fragment parses (json.Unmarshal into the hook's options struct) in a scratch program or CI
  4. After upgrading the hook package, re-check for renamed fields

Example fix

// before — threshold passed as string
"builtins": { "audit_spell": { "enabled": true, "config": { "threshold": "0.8" } } }

// after — correct type
"builtins": { "audit_spell": { "enabled": true, "config": { "threshold": 0.8 } } }
Defensive patterns

Strategy: try-catch

Validate before calling

for name, spec := range cfg.Hooks.Builtins {
    if !spec.Enabled || len(spec.Config) == 0 {
        continue
    }
    var probe any
    if err := json.Unmarshal(spec.Config, &probe); err != nil {
        return fmt.Errorf("hooks.builtins.%s: config is not valid JSON: %w", name, err)
    }
}

Try / catch

if err := al.EnsureHooksInitialized(ctx); err != nil {
    if hookErr, ok := unwrapPrefix(err, "build builtin hook"); ok {
        // config/schema problem: fix config and restart; do not retry blindly
        log.Printf("hook config rejected: %v", hookErr)
    }
}

Prevention

When it happens

Trigger: hooks.builtins.<name>.enabled=true with a config object whose fields are missing, wrong-typed, or out of range for that hook's schema (each RegisterBuiltinHook factory defines its own).

Common situations: Hand-editing hook config JSON; schema drift after upgrading the package providing the hook; nesting settings one level too deep/shallow; passing strings where numbers are expected.

Related errors


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