charmbracelet/crush · error

hook %s[%d]: command is required

Error message

hook %s[%d]: command is required

What it means

ValidateHooks iterates c.Hooks per event and index. Every hook must have a non-empty Command; a hook entry that only sets a matcher (or is otherwise empty) fails validation with 'hook <event>[<i>]: command is required'. This runs during Load and reloadFromDiskLocked before hooks are registered with the runner.

Source

Thrown at internal/config/load.go:1446

// ValidateHooks normalizes event names and checks that every configured
// hook has a command and a syntactically valid matcher regex. Matcher
// compilation used for matching is owned by hooks.Runner; this function
// only validates up front so the user sees config errors at load time
// rather than on the first tool call.
func (c *Config) ValidateHooks() error {
	// Normalize event name keys.
	for event, eventHooks := range c.Hooks {
		canonical := normalizeHookEvent(event)
		if canonical != event {
			c.Hooks[canonical] = append(c.Hooks[canonical], eventHooks...)
			delete(c.Hooks, event)
		}
	}

	for event, eventHooks := range c.Hooks {
		for i, h := range eventHooks {
			if h.Command == "" {
				return fmt.Errorf("hook %s[%d]: command is required", event, i)
			}
			if h.Matcher == "" {
				continue
			}
			if _, err := regexp.Compile(h.Matcher); err != nil {
				return fmt.Errorf("hook %s[%d]: invalid matcher regex %q: %w", event, i, h.Matcher, err)
			}
		}
	}
	return nil
}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Set the command field for the hook named by <event>[<i>] in the error message
  2. Check for key typos ('cmd' vs 'command') that leave Command empty
  3. If the shell variable holding the command is empty at eval time, guard it or give a default
  4. Remove the hook entry entirely if it is no longer needed

Example fix

# before (crushrc)
hook pre_tool_use {
  matcher "bash"
}
# after
hook pre_tool_use {
  matcher "bash"
  command "echo 'bash used'"
}
Defensive patterns

Strategy: validation

Validate before calling

for event, hooks := range cfg.Hooks {
  for i, h := range hooks {
    if h.Command == "" {
      return fmt.Errorf("hook %s[%d] missing command", event, i)
    }
  }
}

Try / catch

if err := config.Load(ctx, ...); err != nil {
  var msg string
  if errors.As(err, new(error)) && strings.Contains(err.Error(), "command is required") {
    // point the user at the exact event[index] named in the message
  }
  _ = msg
}

Prevention

When it happens

Trigger: Load/ValidateHooks encounters a hook in c.Hooks (from the hook builtin in crushrc or the hooks JSON block) whose Command field is the empty string — e.g. hook defined with only a matcher, type, or timeout.

Common situations: Copy-pasted hook block with the command line removed or commented out; YAML/JSON key typo like 'cmd' instead of 'command' so Command stays empty; a hook shell variable that expanded to nothing in crushrc.

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/35d8649a4a765866. Report an issue: GitHub.