Skip to content

feat: implement interceptor hooks system (before/after exec, tool, http, error) #1235

@chaliy

Description

@chaliy

Summary

Add a general-purpose interceptor hooks system to Bash. All hooks are interceptors that can inspect, modify, or cancel data flowing through the execution pipeline.

We already have ExitSignal as the first event (landed in the on_exit implementation). This issue covers expanding to a full hooks system.

Design

Every hook is an interceptor with a uniform return type:

pub enum HookAction<T> {
    Continue(T),     // pass through (possibly modified)
    Cancel(String),  // abort with reason
}

type Interceptor<T> = Box<dyn Fn(T) -> HookAction<T> + Send + Sync>;

Hooks to implement

Hook Data type Use case
before_exec String (script) rewrite/block scripts
after_exec ExecResult modify output, logging
before_tool ToolRequest block/modify tool calls
after_tool ToolResult modify tool output
before_http HttpRequest rewrite URLs, add headers, deny
after_http HttpResponse modify/filter responses
on_exit ExitEvent already implemented via ExitSignal; migrate to hooks
on_error ShellError suppress/modify errors

API

// Builder pattern
let bash = Bash::builder()
    .on_exit(|e| { println!("exit {}", e.code); HookAction::Continue(e) })
    .before_http(|req| {
        if req.url.contains("internal") {
            HookAction::Cancel("blocked".into())
        } else {
            HookAction::Continue(req)
        }
    })
    .build();

// Runtime registration
bash.hooks().after_tool(|result| {
    HookAction::Continue(result)
});

Sync callbacks, async consumers bridge via channels

Hooks are sync (Fn, not async) — they run inline in the interpreter. Async consumers wrap a channel:

let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
bash.hooks().on_exit(Box::new(move |event| {
    let _ = tx.send(event.code);
    HookAction::Continue(event)
}));

Zero cost when unused

if !self.hooks.before_http.is_empty() {
    for hook in &self.hooks.before_http {
        match hook(request) {
            HookAction::Continue(r) => request = r,
            HookAction::Cancel(reason) => return Err(...),
        }
    }
}

Tasks

  • Define HookAction<T>, Interceptor<T>, HookRegistry types
  • Add HookRegistry to Interpreter / Bash
  • Implement before_exec / after_exec hooks
  • Implement before_tool / after_tool hooks
  • Implement before_http / after_http hooks
  • Implement on_error hook
  • Migrate ExitSignal to use the hooks system (keep as convenience API on top)
  • Builder API (.before_http(...), .on_exit(...))
  • Runtime registration API (bash.hooks().on_exit(...))
  • Write spec in specs/
  • Add rustdoc guide in crates/bashkit/docs/
  • Create examples/hooks.rs demonstrating all hook types
  • Tests: interceptor modifies data, cancels operation, multiple hooks chain

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions