Kuberwastaken/claurst · error · anyhow::Error

hooks. hook is missing command

Error message

hooks.{event_name} hook is missing command

What it means

In an imported hooks config, a command hook object under hooks.<event>[].hooks[] lacks the `command` field. The importer requires every hook entry to carry an explicit command string; hooks defined only with type or other keys are rejected.

Solutions

  1. Add "command": "<shell command>" (string) to every hook object under hooks.[].hooks[]
  2. Rename tool-specific keys (cmd, run, script) to "command"
  3. Ensure the command value is a plain quoted string, not a number, array, or object

Example fix

// before
{ "hooks": [ { "type": "command" } ] }
// after
{ "hooks": [ { "command": "./scripts/notify.sh" } ] }
Defensive patterns

Strategy: validation

Validate before calling

for (const groups of Object.values(cfg.hooks ?? {})) {
  for (const g of groups) for (const h of g.hooks ?? []) if (typeof h?.command !== 'string') throw new Error('every hook needs a string "command"');
}

Type guard

const hasCommand = (h) => typeof h === 'object' && h !== null && typeof h.command === 'string' && h.command.length > 0;

Try / catch

try { importConfig(path) } catch (e) { if (String(e).includes('hook is missing command')) { console.error('Add a string "command" field to the named event hook'); process.exitCode = 1; } else { throw e; } }

Prevention

When it happens

Trigger: Importing a hook object like {"type": "command"} without the command value, {"command": 42}, or a hook with only "url"/"prompt" style fields from a different tool's schema.

Common situations: Migrating hooks from a tool whose hook objects use different key names, truncation during copy-paste, or quoting errors that made the command value a nested structure.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/82f6786dda8f4c90. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/core/src/import_config.rs:737

                .as_object()
                .ok_or_else(|| anyhow!("hooks.{event_name}[] must be an object"))?;
            let matcher = entry_obj
                .get("matcher")
                .and_then(Value::as_str)
                .unwrap_or("*")
                .to_string();
            let hooks = entry_obj
                .get("hooks")
                .and_then(Value::as_array)
                .ok_or_else(|| anyhow!("hooks.{event_name}[].hooks must be an array"))?;
            for hook in hooks {
                let hook_obj = hook
                    .as_object()
                    .ok_or_else(|| anyhow!("hooks.{event_name}[].hooks[] must be an object"))?;
                let command = hook_obj
                    .get("command")
                    .and_then(Value::as_str)
                    .ok_or_else(|| anyhow!("hooks.{event_name} hook is missing command"))?
                    .to_string();
                hook_entries.push(HookEntry {
                    command,
                    tool_filter: if matcher == "*" { None } else { Some(matcher.clone()) },
                    blocking: false,
                });
            }
        }
        out.insert(event, hook_entries);
    }
    Ok(out)
}

fn parse_hook_event(name: &str) -> Result<HookEvent> {
    match name {
        "PreToolUse" => Ok(HookEvent::PreToolUse),
        "PostToolUse" => Ok(HookEvent::PostToolUse),
        "Stop" => Ok(HookEvent::Stop),

View on GitHub (pinned to b0637c97ec)