Expose hook execution mode

This commit is contained in:
Abhinav Vedmala
2026-07-09 22:15:07 +00:00
parent b721077974
commit 7bef7e9936
20 changed files with 74 additions and 3 deletions
@@ -10849,6 +10849,14 @@
"eventName": {
"$ref": "#/definitions/v2/HookEventName"
},
"executionMode": {
"allOf": [
{
"$ref": "#/definitions/v2/HookExecutionMode"
}
],
"default": "sync"
},
"handlerType": {
"$ref": "#/definitions/v2/HookHandlerType"
},
@@ -7200,6 +7200,14 @@
"eventName": {
"$ref": "#/definitions/HookEventName"
},
"executionMode": {
"allOf": [
{
"$ref": "#/definitions/HookExecutionMode"
}
],
"default": "sync"
},
"handlerType": {
"$ref": "#/definitions/HookHandlerType"
},
@@ -35,6 +35,13 @@
],
"type": "string"
},
"HookExecutionMode": {
"enum": [
"sync",
"async"
],
"type": "string"
},
"HookHandlerType": {
"enum": [
"command",
@@ -64,6 +71,14 @@
"eventName": {
"$ref": "#/definitions/HookEventName"
},
"executionMode": {
"allOf": [
{
"$ref": "#/definitions/HookExecutionMode"
}
],
"default": "sync"
},
"handlerType": {
"$ref": "#/definitions/HookHandlerType"
},
@@ -3,8 +3,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
import type { HookEventName } from "./HookEventName";
import type { HookExecutionMode } from "./HookExecutionMode";
import type { HookHandlerType } from "./HookHandlerType";
import type { HookSource } from "./HookSource";
import type { HookTrustStatus } from "./HookTrustStatus";
export type HookMetadata = { key: string, eventName: HookEventName, handlerType: HookHandlerType, matcher: string | null, command: string | null, timeoutSec: bigint, statusMessage: string | null, sourcePath: AbsolutePathBuf, source: HookSource, pluginId: string | null, displayOrder: bigint, enabled: boolean, isManaged: boolean, currentHash: string, trustStatus: HookTrustStatus, };
export type HookMetadata = { key: string, eventName: HookEventName, handlerType: HookHandlerType, executionMode: HookExecutionMode, matcher: string | null, command: string | null, timeoutSec: bigint, statusMessage: string | null, sourcePath: AbsolutePathBuf, source: HookSource, pluginId: string | null, displayOrder: bigint, enabled: boolean, isManaged: boolean, currentHash: string, trustStatus: HookTrustStatus, };
@@ -28,8 +28,11 @@ v2_enum_from_core!(
);
v2_enum_from_core!(
#[derive(Default)]
pub enum HookExecutionMode from CoreHookExecutionMode {
Sync, Async
#[default]
Sync,
Async
}
);
@@ -1,5 +1,6 @@
use super::AppSummary;
use super::HookEventName;
use super::HookExecutionMode;
use super::HookHandlerType;
use super::HookSource;
use super::HookTrustStatus;
@@ -512,6 +513,8 @@ pub struct HookMetadata {
pub key: String,
pub event_name: HookEventName,
pub handler_type: HookHandlerType,
#[serde(default)]
pub execution_mode: HookExecutionMode,
pub matcher: Option<String>,
pub command: Option<String>,
pub timeout_sec: u64,
+3
View File
@@ -1722,6 +1722,8 @@ For linked Git worktrees, project hook declarations come from the matching `.cod
Hooks are returned even when disabled so clients can render and re-enable them. User-controlled state lives under `hooks.state`. Managed hooks are non-configurable, and user entries for managed hook keys are ignored during loading.
`executionMode` reports how a command hook runs. `sync` hooks participate in the current operation, while `async` hooks run in the background and can only contribute informational output to a later accepted fresh user turn.
For unmanaged hooks, `currentHash` and `trustStatus` describe whether the current definition is first-seen, approved, or changed since approval. Only trusted unmanaged hooks become runnable. Hook keys combine the source identity with a trailing event/group/handler selector that is currently positional.
```json
@@ -1744,6 +1746,7 @@ For unmanaged hooks, `currentHash` and `trustStatus` describe whether the curren
"key": "/Users/me/.codex/config.toml:pre_tool_use:0:0",
"eventName": "pre_tool_use",
"handlerType": "command",
"executionMode": "sync",
"isManaged": false,
"matcher": "Bash",
"command": "python3 /Users/me/hook.py",
@@ -68,6 +68,7 @@ fn hooks_to_info(hooks: &[codex_hooks::HookListEntry]) -> Vec<HookMetadata> {
key: hook.key.clone(),
event_name: hook.event_name.into(),
handler_type: hook.handler_type.into(),
execution_mode: hook.execution_mode.into(),
matcher: hook.matcher.clone(),
command: hook.command.clone(),
timeout_sec: hook.timeout_sec,
@@ -8,6 +8,7 @@ use app_test_support::to_response;
use codex_app_server_protocol::ConfigBatchWriteParams;
use codex_app_server_protocol::ConfigEdit;
use codex_app_server_protocol::HookEventName;
use codex_app_server_protocol::HookExecutionMode;
use codex_app_server_protocol::HookHandlerType;
use codex_app_server_protocol::HookMetadata;
use codex_app_server_protocol::HookSource;
@@ -46,6 +47,7 @@ fn command_hook_hash(
matcher: Option<&str>,
command: &str,
timeout_sec: u64,
execution_mode: HookExecutionMode,
status_message: Option<&str>,
) -> String {
let identity = NormalizedHookIdentity {
@@ -56,7 +58,7 @@ fn command_hook_hash(
command: command.to_string(),
command_windows: None,
timeout_sec: Some(timeout_sec),
r#async: false,
r#async: execution_mode == HookExecutionMode::Async,
status_message: status_message.map(ToOwned::to_owned),
}],
},
@@ -79,6 +81,7 @@ matcher = "Bash"
type = "command"
command = "python3 /tmp/listed-hook.py"
timeout = 5
async = true
statusMessage = "running listed hook"
"#,
)?;
@@ -164,6 +167,7 @@ async fn hooks_list_shows_discovered_hook() -> Result<()> {
key: format!("{}:pre_tool_use:0:0", config_path.as_path().display()),
event_name: HookEventName::PreToolUse,
handler_type: HookHandlerType::Command,
execution_mode: HookExecutionMode::Async,
matcher: Some("Bash".to_string()),
command: Some("python3 /tmp/listed-hook.py".to_string()),
timeout_sec: 5,
@@ -179,6 +183,7 @@ async fn hooks_list_shows_discovered_hook() -> Result<()> {
Some("Bash"),
"python3 /tmp/listed-hook.py",
/*timeout_sec*/ 5,
HookExecutionMode::Async,
Some("running listed hook"),
),
trust_status: HookTrustStatus::Untrusted,
@@ -246,6 +251,7 @@ async fn hooks_list_shows_discovered_plugin_hook() -> Result<()> {
key: "demo@test:hooks/hooks.json:pre_tool_use:0:0".to_string(),
event_name: HookEventName::PreToolUse,
handler_type: HookHandlerType::Command,
execution_mode: HookExecutionMode::Sync,
matcher: Some("Bash".to_string()),
command: Some("echo plugin hook".to_string()),
timeout_sec: 7,
@@ -261,6 +267,7 @@ async fn hooks_list_shows_discovered_plugin_hook() -> Result<()> {
Some("Bash"),
"echo plugin hook",
/*timeout_sec*/ 7,
HookExecutionMode::Sync,
Some("running plugin hook"),
),
trust_status: HookTrustStatus::Untrusted,
@@ -460,6 +467,7 @@ timeout = 5
),
event_name: HookEventName::PreToolUse,
handler_type: HookHandlerType::Command,
execution_mode: HookExecutionMode::Sync,
matcher: Some("Bash".to_string()),
command: Some("echo project hook".to_string()),
timeout_sec: 5,
@@ -475,6 +483,7 @@ timeout = 5
Some("Bash"),
"echo project hook",
/*timeout_sec*/ 5,
HookExecutionMode::Sync,
/*status_message*/ None,
),
trust_status: HookTrustStatus::Untrusted,
+1
View File
@@ -522,6 +522,7 @@ fn append_matcher_groups(
is_managed: source.is_managed,
current_hash,
trust_status,
execution_mode,
});
if enabled
&& (source.bypass_hook_trust
+1
View File
@@ -98,6 +98,7 @@ pub struct HookListEntry {
pub is_managed: bool,
pub current_hash: String,
pub trust_status: HookTrustStatus,
pub execution_mode: codex_protocol::protocol::HookExecutionMode,
}
#[derive(Clone)]
@@ -1,4 +1,5 @@
use codex_app_server_protocol::HookEventName;
use codex_app_server_protocol::HookExecutionMode;
use codex_app_server_protocol::HookMetadata;
use codex_app_server_protocol::HookSource;
use codex_app_server_protocol::HookTrustStatus;
@@ -491,6 +492,11 @@ impl HooksBrowserView {
width,
Some(MAX_COMMAND_DETAIL_LINES),
));
let execution_mode = match hook.execution_mode {
HookExecutionMode::Sync => "Sync",
HookExecutionMode::Async => "Async",
};
lines.push(detail_line("Mode", execution_mode));
lines.push(detail_line("Timeout", &format!("{}s", hook.timeout_sec)));
lines.push(detail_line("Trust", hook_trust_label(hook.trust_status)));
lines
@@ -865,6 +871,7 @@ mod tests {
use crate::test_support::test_path_display;
use codex_app_server_protocol::HookErrorInfo;
use codex_app_server_protocol::HookEventName;
use codex_app_server_protocol::HookExecutionMode;
use codex_app_server_protocol::HookHandlerType;
use codex_app_server_protocol::HookMetadata;
use codex_app_server_protocol::HookSource;
@@ -929,6 +936,7 @@ mod tests {
key: key.to_string(),
event_name,
handler_type: HookHandlerType::Command,
execution_mode: HookExecutionMode::Sync,
is_managed,
matcher: Some("Bash".to_string()),
command: Some(command.to_string()),
@@ -1092,6 +1100,7 @@ mod tests {
/*display_order*/ 0,
);
untrusted_hook.trust_status = HookTrustStatus::Untrusted;
untrusted_hook.execution_mode = HookExecutionMode::Async;
let mut view = HooksBrowserView::new(
vec![untrusted_hook],
Vec::new(),
@@ -14,6 +14,7 @@ expression: "render_lines(&view, 44)"
Command one two three four five six
seven eight nine ten eleven
twelve thirteen fourteen…
Mode Sync
Timeout 30s
Trust Trusted
@@ -13,6 +13,7 @@ expression: "render_lines(&view, 112)"
Matcher Bash
Source Plugin - superpowers@openai-curated
Command ${CODEX_PLUGIN_ROOT}/hooks/pre-tool-use-check.sh
Mode Sync
Timeout 30s
Trust Trusted
@@ -12,6 +12,7 @@ expression: "render_lines(&view, 112)"
Matcher Bash
Source Admin config
Command /enterprise/hooks/permission-check.sh
Mode Sync
Timeout 30s
Trust Managed
@@ -12,6 +12,7 @@ expression: "render_lines(&view, 112)"
Matcher Bash
Source User config - /tmp/hooks.json
Command /tmp/pre-tool-use-check.sh
Mode Sync
Timeout 30s
Trust New hook - review required
@@ -19,6 +19,7 @@ expression: "render_lines(&view, 112)"
Matcher Bash
Source User config - /tmp/hooks.json
Command /tmp/hook-8.sh
Mode Sync
Timeout 30s
Trust Trusted
@@ -13,6 +13,7 @@ expression: "render_lines(&view, 112)"
Matcher Bash
Source Admin config
Command /enterprise/hooks/pre-tool-use-2.sh
Mode Sync
Timeout 30s
Trust Managed
@@ -12,6 +12,7 @@ expression: "render_lines(&view, 112)"
Matcher Bash
Source User config - /tmp/hooks.json
Command ~/bin/untrusted.sh
Mode Async
Timeout 30s
Trust New hook - review required
+2
View File
@@ -291,6 +291,7 @@ mod tests {
use crate::test_support::PathBufExt;
use crate::test_support::test_path_buf;
use codex_app_server_protocol::HookEventName;
use codex_app_server_protocol::HookExecutionMode;
use codex_app_server_protocol::HookHandlerType;
use codex_app_server_protocol::HookMetadata;
use codex_app_server_protocol::HookSource;
@@ -306,6 +307,7 @@ mod tests {
key: key.to_string(),
event_name: HookEventName::PreToolUse,
handler_type: HookHandlerType::Command,
execution_mode: HookExecutionMode::Sync,
is_managed: false,
matcher: Some("Bash".to_string()),
command: Some("/tmp/hook.sh".to_string()),