sipeed/picoclaw · error

no hook modes enabled

Error message

no hook modes enabled

What it means

Thrown by processHookOptionsFromConfig when a process hook is configured but ends up with zero modes enabled: no observe entries produced a mode and no intercept entries matched. The library refuses to mount a hook that would receive no events at all, since it could never do anything.

Source

Thrown at pkg/agent/hook_mount.go:263

	opts.ObserveKinds = observeKinds

	for _, intercept := range spec.Intercept {
		switch intercept {
		case "before_llm", "after_llm":
			opts.InterceptLLM = true
		case "before_tool", "after_tool":
			opts.InterceptTool = true
		case "approve_tool":
			opts.ApproveTool = true
		case "":
			continue
		default:
			return ProcessHookOptions{}, fmt.Errorf("unsupported intercept %q", intercept)
		}
	}

	if !opts.Observe && !opts.InterceptLLM && !opts.InterceptTool && !opts.ApproveTool {
		return ProcessHookOptions{}, fmt.Errorf("no hook modes enabled")
	}

	return opts, nil
}

func processHookEnvFromMap(envMap map[string]string) []string {
	if len(envMap) == 0 {
		return nil
	}

	keys := make([]string, 0, len(envMap))
	for key := range envMap {
		keys = append(keys, key)
	}
	sort.Strings(keys)

	env := make([]string, 0, len(keys))
	for _, key := range keys {

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Add at least one mode to the hook: an observe list (e.g. observe: ["*"]) or an intercept entry (e.g. intercept: ["before_tool"])
  2. If the hook is not meant to run, remove the entry or set enabled: false instead of leaving a mode-less enabled hook

Example fix

# before
hooks:
  process:
    my-hook:
      enabled: true
      command: ["./hook"]

# after
hooks:
  process:
    my-hook:
      enabled: true
      command: ["./hook"]
      observe: ["*"]
      intercept: ["before_tool"]
Defensive patterns

Strategy: validation

Validate before calling

func hookHasMode(spec config.ProcessHookConfig) bool {
    if len(spec.Observe) > 0 {
        return true // note: observe: [""] alone does NOT enable a mode
    }
    for _, v := range spec.Intercept {
        if v != "" {
            return true
        }
    }
    return false
}

if spec.Enabled && !hookHasMode(spec) {
    return fmt.Errorf("hook %s enabled without observe or intercept", name)
}

Try / catch

opts, err := processHookOptionsFromConfig(spec)
if err != nil {
    return fmt.Errorf("hook %q misconfigured: %w", name, err)
}

Prevention

When it happens

Trigger: A hook config with `enabled: true` (and a command) that declares neither `observe` nor `intercept`, or declares only empty strings — e.g. observe: [""] or intercept: [""], both of which are skipped. Note that observe: [] is falsy for the Observe flag.

Common situations: Minimal copy-pasted hook config that only sets command; a config where observe/intercept keys exist but are commented out; refactoring that accidentally drops the mode lists.

Related errors


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