atuinsh/atuin · error

expected `atuin hook

Error message

expected `atuin hook <agent>` or `atuin hook install <agent>`

What it means

`atuin hook` supports two forms: `atuin hook <agent>` (fire the hook) or `atuin hook install <agent>` (install it). Running it with neither an action nor an agent gives no usable target, so `run` bails with usage guidance.

Solutions

  1. Pass an agent: `atuin hook <agent>` to fire, or `atuin hook install <agent>` to install.
  2. Check `atuin hook --help` for valid agents.
  3. If invoked from a shell hook, verify the agent variable is set and quoted in the hook script.

Example fix

# before
atuin hook

# after
atuin hook install claude  # or: atuin hook claude
Defensive patterns

Strategy: validation

Validate before calling

# shell: validate args before calling
case "$#" in
  0) echo "usage: atuin hook <agent> | atuin hook install <agent>"; exit 1;;
esac

Try / catch

atuin hook "$AGENT" || { echo "hook failed; pass an agent, e.g. atuin hook claude"; exit 1; }

Prevention

When it happens

Trigger: Running bare `atuin hook` with no arguments — the match arm `(None, None)` in `HookCmd::run`.

Common situations: Typing the subcommand to explore it without args; a shell hook script losing its argument due to quoting bugs or an unset variable.

Understand the failure class

Background: "no subcommand specified" and "... is required": CLI errors when a required argument is missing — this error's family across 13 libraries.

Related errors


AI-assisted analysis of atuinsh/atuin@900007c480 (2026-09-19). Data as JSON: /api/errors/4b1f01d9a6592385. Report an issue: GitHub.

Appendix: source

Thrown at crates/atuin/src/command/client/hook.rs:47

#[derive(Parser, Debug)]
#[command(infer_subcommands = true, args_conflicts_with_subcommands = true)]
pub struct Cmd {
    #[command(subcommand)]
    action: Option<Action>,

    /// Which agent's hook format to parse (e.g., "claude-code")
    #[arg(value_name = "AGENT", hide = true)]
    agent: Option<String>,
}

impl Cmd {
    #[instrument(level = "trace", skip_all, err)]
    pub async fn run(self, settings: &Settings) -> Result<()> {
        match (self.action, self.agent) {
            (Some(Action::Install { agent }), None) => install(&agent).await,
            (None, Some(agent)) => handle(&agent, settings).await,
            (None, None) => {
                bail!("expected `atuin hook <agent>` or `atuin hook install <agent>`");
            }
            (Some(_), Some(_)) => {
                bail!("hook action cannot be combined with a positional agent");
            }
        }
    }
}

fn id_file_path(tool_use_id: &str) -> PathBuf {
    std::env::temp_dir().join(format!("atuin-hook-{tool_use_id}"))
}

/// How the user makes an agent pick up a freshly installed extension. Only the
/// extension-based agents need a nudge; command-hook agents load on next run.
fn reload_hint(harness_name: &str) -> Option<&'static str> {
    match harness_name {
        "pi" => Some("Reload pi with `/reload` or restart pi."),
        "opencode" => Some("Restart opencode to load the plugin."),

View on GitHub (pinned to 900007c480)