Show only runnable hooks as active

This commit is contained in:
Abhinav Vedmala
2026-05-01 15:57:31 -07:00
parent bcb465f4cb
commit 3e67b50a21
2 changed files with 90 additions and 9 deletions
@@ -2,6 +2,7 @@ use codex_app_server_protocol::HookErrorInfo;
use codex_app_server_protocol::HookEventName;
use codex_app_server_protocol::HookMetadata;
use codex_app_server_protocol::HookSource;
use codex_app_server_protocol::HookTrustStatus;
use crossterm::event::KeyCode;
use crossterm::event::KeyEvent;
use crossterm::event::KeyModifiers;
@@ -84,9 +85,7 @@ impl HooksBrowserView {
let active = self
.hooks
.iter()
.filter(|hook| {
hook.event_name == event_name && (hook.enabled || hook.is_managed)
})
.filter(|hook| hook.event_name == event_name && hook_is_active(hook))
.count();
EventRow {
event_name,
@@ -292,11 +291,7 @@ impl HooksBrowserView {
self.handlers_for_event(event_name)
.enumerate()
.map(|(idx, hook)| {
let marker = if hook.enabled || hook.is_managed {
'x'
} else {
' '
};
let marker = if hook_is_active(hook) { 'x' } else { ' ' };
let row = format!("[{marker}] {}", hook_title(idx));
let mut line = Line::from(row);
line = truncate_line_with_ellipsis_if_overflow(line, width);
@@ -525,6 +520,14 @@ impl Renderable for HooksBrowserView {
}
}
fn hook_is_active(hook: &HookMetadata) -> bool {
hook.enabled
&& matches!(
hook.trust_status,
HookTrustStatus::Managed | HookTrustStatus::Trusted
)
}
struct EventRow {
event_name: HookEventName,
installed: usize,
@@ -805,6 +808,35 @@ mod tests {
assert_snapshot!("hooks_browser_handlers", render_lines(&view, /*width*/ 112));
}
#[test]
fn renders_untrusted_enabled_handler_as_inactive() {
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
let mut untrusted_hook = hook(
"path:untrusted",
HookEventName::PreToolUse,
HookSource::User,
/*plugin_id*/ None,
"~/bin/untrusted.sh",
/*enabled*/ true,
/*is_managed*/ false,
/*display_order*/ 0,
);
untrusted_hook.trusted_hash = None;
untrusted_hook.trust_status = HookTrustStatus::Untrusted;
let mut view = HooksBrowserView::new(
vec![untrusted_hook],
Vec::new(),
Vec::new(),
AppEventSender::new(tx_raw),
);
view.handle_key_event(KeyEvent::from(KeyCode::Enter));
assert_snapshot!(
"hooks_browser_untrusted_enabled_handler",
render_lines(&view, /*width*/ 112)
);
}
#[test]
fn renders_managed_handler_without_toggle_hint() {
let mut view = view();
@@ -937,7 +969,7 @@ mod tests {
HookSource::System,
/*plugin_id*/ None,
"/enterprise/hooks/pre-tool-use-check.sh",
/*enabled*/ false,
/*enabled*/ true,
/*is_managed*/ true,
/*display_order*/ 0,
)],
@@ -956,6 +988,38 @@ mod tests {
assert_eq!(pre_tool_use.active, 1);
}
#[test]
fn untrusted_enabled_hooks_do_not_count_as_active() {
let (tx_raw, _rx) = unbounded_channel::<AppEvent>();
let mut untrusted_hook = hook(
"path:untrusted",
HookEventName::PreToolUse,
HookSource::User,
/*plugin_id*/ None,
"~/bin/untrusted.sh",
/*enabled*/ true,
/*is_managed*/ false,
/*display_order*/ 0,
);
untrusted_hook.trusted_hash = None;
untrusted_hook.trust_status = HookTrustStatus::Untrusted;
let view = HooksBrowserView::new(
vec![untrusted_hook],
Vec::new(),
Vec::new(),
AppEventSender::new(tx_raw),
);
let rows = view.event_rows();
let pre_tool_use = rows
.into_iter()
.find(|row| row.event_name == HookEventName::PreToolUse)
.expect("pre tool use row");
assert_eq!(pre_tool_use.installed, 1);
assert_eq!(pre_tool_use.active, 0);
}
fn assert_unmanaged_toggle_key(key_code: KeyCode) {
let (tx_raw, mut rx) = unbounded_channel::<AppEvent>();
let mut view = HooksBrowserView::new(
@@ -0,0 +1,17 @@
---
source: tui/src/bottom_pane/hooks_browser_view.rs
expression: "render_lines(&view, 112)"
---
PreToolUse hooks
Turn hooks on or off. Your changes are saved automatically.
[ ] Hook 1
Event PreToolUse
Matcher Bash
Source User config - /tmp/hooks.json
Command ~/bin/untrusted.sh
Timeout 30s
Press space or enter to toggle; esc to go back