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
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
ExitSignalas the first event (landed in theon_exitimplementation). This issue covers expanding to a full hooks system.Design
Every hook is an interceptor with a uniform return type:
Hooks to implement
before_execString(script)after_execExecResultbefore_toolToolRequestafter_toolToolResultbefore_httpHttpRequestafter_httpHttpResponseon_exitExitEventExitSignal; migrate to hookson_errorShellErrorAPI
Sync callbacks, async consumers bridge via channels
Hooks are sync (
Fn, not async) — they run inline in the interpreter. Async consumers wrap a channel:Zero cost when unused
Tasks
HookAction<T>,Interceptor<T>,HookRegistrytypesHookRegistrytoInterpreter/Bashbefore_exec/after_exechooksbefore_tool/after_toolhooksbefore_http/after_httphookson_errorhookExitSignalto use the hooks system (keep as convenience API on top).before_http(...),.on_exit(...))bash.hooks().on_exit(...))specs/crates/bashkit/docs/examples/hooks.rsdemonstrating all hook types