add --dangerously-bypass-hook-trust CLI flag (#21768)

# Why

Hook trust happens through the TUI in `/hooks` so it can block
non-interactive use cases. This flag will allow users that are using
codex headlessly to bypass hooks when they want to.

# What

This adds one invocation-scoped escape hatch.

- the CLI flag sets a runtime-only `bypass_hook_trust` override; there
is no durable `config.toml` setting
- hook discovery still respects normal enablement, so explicitly
disabled hooks remain disabled
- we show a `--dangerously-bypass-hook-trust is enabled. Enabled hooks
may run without review for this invocation.` message on startup so
accidental use is visible in both interactive and exec flows

This keeps “enabled” and “trusted” as separate concepts in the normal
path, while giving CI/E2E callers a stable way to opt into the
exceptional path when they already control the hook set.
This commit is contained in:
Abhinav
2026-05-13 03:13:57 -04:00
committed by GitHub
Unverified
parent 934a40c7d9
commit 392e94e9ea
16 changed files with 252 additions and 16 deletions
@@ -525,6 +525,7 @@ impl CatalogRequestProcessor {
};
let hooks = codex_hooks::list_hooks(codex_hooks::HooksConfig {
feature_enabled: config.features.enabled(Feature::CodexHooks),
bypass_hook_trust: config.bypass_hook_trust,
config_layer_stack: Some(config.config_layer_stack),
plugin_hook_sources: plugin_outcome.effective_plugin_hook_sources(),
plugin_hook_load_warnings: plugin_outcome.effective_plugin_hook_warnings(),
+13
View File
@@ -1496,6 +1496,7 @@ async fn run_debug_prompt_input_command(
main_execve_wrapper_exe: arg0_paths.main_execve_wrapper_exe,
show_raw_agent_reasoning: shared.oss.then_some(true),
ephemeral: Some(true),
bypass_hook_trust: shared.bypass_hook_trust.then_some(true),
additional_writable_roots: shared.add_dir,
..Default::default()
};
@@ -2369,6 +2370,18 @@ mod tests {
assert_eq!(interactive.resume_session_id, None);
}
#[test]
fn resume_merges_bypass_hook_trust_flag() {
let interactive = finalize_resume_from_args(
["codex", "resume", "--dangerously-bypass-hook-trust"].as_ref(),
);
assert!(interactive.bypass_hook_trust);
assert!(interactive.resume_picker);
assert!(!interactive.resume_last);
assert_eq!(interactive.resume_session_id, None);
}
#[test]
fn fork_picker_logic_none_and_not_last() {
let interactive = finalize_fork_from_args(["codex", "fork"].as_ref());
+27
View File
@@ -7322,6 +7322,7 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
startup_warnings: Vec::new(),
history: History::default(),
ephemeral: false,
bypass_hook_trust: false,
file_opener: UriBasedFileOpener::VsCode,
codex_self_exe: None,
codex_linux_sandbox_exe: None,
@@ -7769,6 +7770,7 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
startup_warnings: Vec::new(),
history: History::default(),
ephemeral: false,
bypass_hook_trust: false,
file_opener: UriBasedFileOpener::VsCode,
codex_self_exe: None,
codex_linux_sandbox_exe: None,
@@ -7930,6 +7932,7 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
startup_warnings: Vec::new(),
history: History::default(),
ephemeral: false,
bypass_hook_trust: false,
file_opener: UriBasedFileOpener::VsCode,
codex_self_exe: None,
codex_linux_sandbox_exe: None,
@@ -8076,6 +8079,7 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
startup_warnings: Vec::new(),
history: History::default(),
ephemeral: false,
bypass_hook_trust: false,
file_opener: UriBasedFileOpener::VsCode,
codex_self_exe: None,
codex_linux_sandbox_exe: None,
@@ -8973,6 +8977,29 @@ async fn active_profile_is_cleared_when_requirements_force_fallback() -> std::io
Ok(())
}
#[tokio::test]
async fn bypass_hook_trust_adds_startup_warning() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
let config = ConfigBuilder::without_managed_config_for_tests()
.codex_home(codex_home.path().to_path_buf())
.fallback_cwd(Some(codex_home.path().to_path_buf()))
.harness_overrides(ConfigOverrides {
bypass_hook_trust: Some(true),
..Default::default()
})
.build()
.await?;
assert!(
config.startup_warnings.iter().any(|warning| warning
== "`--dangerously-bypass-hook-trust` is enabled. Enabled hooks may run without review for this invocation."),
"{:?}",
config.startup_warnings
);
Ok(())
}
#[tokio::test]
async fn permission_profile_override_preserves_split_write_roots() -> std::io::Result<()> {
let codex_home = TempDir::new()?;
+16
View File
@@ -668,6 +668,11 @@ pub struct Config {
/// When true, session is not persisted on disk. Default to `false`
pub ephemeral: bool,
/// Whether enabled hooks should run without requiring persisted hook trust for this session.
///
/// This is a runtime-only knob populated from invocation overrides, not from config files.
pub bypass_hook_trust: bool,
/// Optional URI-based file opener. If set, citations to files in the model
/// output will be hyperlinked using the specified URI scheme.
pub file_opener: UriBasedFileOpener,
@@ -1886,6 +1891,7 @@ pub struct ConfigOverrides {
pub show_raw_agent_reasoning: Option<bool>,
pub tools_web_search_request: Option<bool>,
pub ephemeral: Option<bool>,
pub bypass_hook_trust: Option<bool>,
/// Additional directories that should be treated as writable roots for this session.
pub additional_writable_roots: Vec<PathBuf>,
}
@@ -2169,8 +2175,17 @@ impl Config {
show_raw_agent_reasoning,
tools_web_search_request: override_tools_web_search_request,
ephemeral,
bypass_hook_trust,
additional_writable_roots,
} = overrides;
let bypass_hook_trust = bypass_hook_trust.unwrap_or_default();
if bypass_hook_trust {
startup_warnings.push(
"`--dangerously-bypass-hook-trust` is enabled. Enabled hooks may run without review for this invocation."
.to_string(),
);
}
if sandbox_mode.is_some() && permission_profile.is_some() {
return Err(std::io::Error::new(
@@ -3103,6 +3118,7 @@ impl Config {
config_layer_stack,
history,
ephemeral: ephemeral.unwrap_or_default(),
bypass_hook_trust,
file_opener: cfg.file_opener.unwrap_or(UriBasedFileOpener::VsCode),
codex_self_exe,
codex_linux_sandbox_exe,
+1
View File
@@ -3362,6 +3362,7 @@ async fn build_hooks_for_config(
Hooks::new(HooksConfig {
legacy_notify_argv: config.notify.clone(),
feature_enabled: config.features.enabled(Feature::CodexHooks),
bypass_hook_trust: config.bypass_hook_trust,
config_layer_stack: Some(config.config_layer_stack.clone()),
plugin_hook_sources,
plugin_hook_load_warnings,
+1
View File
@@ -155,6 +155,7 @@ fn mark_exec_global_args(cmd: clap::Command) -> clap::Command {
.mut_arg("dangerously_bypass_approvals_and_sandbox", |arg| {
arg.global(true)
})
.mut_arg("bypass_hook_trust", |arg| arg.global(true))
}
#[derive(Debug, clap::Subcommand)]
+2
View File
@@ -264,6 +264,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
config_profile,
sandbox_mode: sandbox_mode_cli_arg,
dangerously_bypass_approvals_and_sandbox,
bypass_hook_trust,
cwd,
add_dir,
} = shared;
@@ -422,6 +423,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
show_raw_agent_reasoning: oss.then_some(true),
tools_web_search_request: None,
ephemeral: ephemeral.then_some(true),
bypass_hook_trust: bypass_hook_trust.then_some(true),
additional_writable_roots: add_dir,
};
+98 -4
View File
@@ -41,6 +41,7 @@ struct HookHandlerSource<'a> {
key_source: String,
source: HookSource,
is_managed: bool,
bypass_hook_trust: bool,
hook_states: &'a HashMap<String, HookStateToml>,
env: HashMap<String, String>,
plugin_id: Option<String>,
@@ -49,6 +50,7 @@ struct HookHandlerSource<'a> {
#[derive(Clone, Copy)]
struct HookDiscoveryPolicy {
allow_managed_hooks_only: bool,
bypass_hook_trust: bool,
}
impl HookDiscoveryPolicy {
@@ -61,6 +63,7 @@ pub(crate) fn discover_handlers(
config_layer_stack: Option<&ConfigLayerStack>,
plugin_hook_sources: Vec<PluginHookSource>,
plugin_hook_load_warnings: Vec<String>,
bypass_hook_trust: bool,
) -> DiscoveryResult {
let mut handlers = Vec::new();
let mut hook_entries = Vec::new();
@@ -75,6 +78,7 @@ pub(crate) fn discover_handlers(
.as_ref()
.is_some_and(|requirement| requirement.value)
}),
bypass_hook_trust,
};
if let Some(config_layer_stack) = config_layer_stack {
@@ -99,6 +103,7 @@ pub(crate) fn discover_handlers(
key_source: policy_path.display().to_string(),
source: hook_source,
is_managed,
bypass_hook_trust: false,
hook_states: &hook_states,
env: HashMap::new(),
plugin_id: None,
@@ -132,6 +137,7 @@ pub(crate) fn discover_handlers(
key_source: source_path.display().to_string(),
source: hook_source,
is_managed,
bypass_hook_trust: policy.bypass_hook_trust,
hook_states: &hook_states,
env: HashMap::new(),
plugin_id: None,
@@ -183,6 +189,7 @@ fn append_managed_requirement_handlers(
key_source: source_path.display().to_string(),
source: hook_source_for_requirement_source(managed_hooks.source.as_ref()),
is_managed: true,
bypass_hook_trust: false,
hook_states,
env: HashMap::new(),
plugin_id: None,
@@ -233,6 +240,7 @@ fn append_plugin_hook_sources(
),
source: HookSource::Plugin,
is_managed: false,
bypass_hook_trust: policy.bypass_hook_trust,
hook_states,
env,
plugin_id: Some(plugin_id),
@@ -485,10 +493,11 @@ fn append_matcher_groups(
trust_status,
});
if enabled
&& matches!(
trust_status,
HookTrustStatus::Managed | HookTrustStatus::Trusted
)
&& (source.bypass_hook_trust
|| matches!(
trust_status,
HookTrustStatus::Managed | HookTrustStatus::Trusted
))
{
handlers.push(ConfiguredHandler {
event_name,
@@ -620,6 +629,7 @@ mod tests {
use codex_config::HookStateToml;
use codex_config::MatcherGroup;
use codex_config::TomlValue;
use codex_protocol::protocol::HookTrustStatus;
fn source_path() -> AbsolutePathBuf {
test_path_buf("/tmp/hooks.json").abs()
@@ -638,6 +648,24 @@ mod tests {
key_source: path.display().to_string(),
source: hook_source(),
is_managed: true,
bypass_hook_trust: false,
hook_states,
env: std::collections::HashMap::new(),
plugin_id: None,
}
}
fn unmanaged_hook_handler_source<'a>(
path: &'a AbsolutePathBuf,
hook_states: &'a std::collections::HashMap<String, HookStateToml>,
bypass_hook_trust: bool,
) -> super::HookHandlerSource<'a> {
super::HookHandlerSource {
path,
key_source: path.display().to_string(),
source: HookSource::User,
is_managed: false,
bypass_hook_trust,
hook_states,
env: std::collections::HashMap::new(),
plugin_id: None,
@@ -727,6 +755,72 @@ mod tests {
);
}
#[test]
fn bypass_hook_trust_allows_enabled_untrusted_handlers() {
let mut handlers = Vec::new();
let mut hook_entries = Vec::new();
let mut warnings = Vec::new();
let mut display_order = 0;
let source_path = source_path();
let hook_states = std::collections::HashMap::new();
append_matcher_groups(
&mut handlers,
&mut hook_entries,
&mut warnings,
&mut display_order,
&unmanaged_hook_handler_source(
&source_path,
&hook_states,
/*bypass_hook_trust*/ true,
),
HookEventName::PreToolUse,
vec![command_group(Some("Bash"))],
);
assert_eq!(warnings, Vec::<String>::new());
assert_eq!(handlers.len(), 1);
assert_eq!(hook_entries.len(), 1);
assert_eq!(hook_entries[0].trust_status, HookTrustStatus::Untrusted);
assert_eq!(hook_entries[0].enabled, true);
}
#[test]
fn bypass_hook_trust_respects_disabled_handlers() {
let mut handlers = Vec::new();
let mut hook_entries = Vec::new();
let mut warnings = Vec::new();
let mut display_order = 0;
let source_path = source_path();
let hook_states = std::collections::HashMap::from([(
format!("{}:pre_tool_use:0:0", source_path.display()),
HookStateToml {
enabled: Some(false),
trusted_hash: None,
},
)]);
append_matcher_groups(
&mut handlers,
&mut hook_entries,
&mut warnings,
&mut display_order,
&unmanaged_hook_handler_source(
&source_path,
&hook_states,
/*bypass_hook_trust*/ true,
),
HookEventName::PreToolUse,
vec![command_group(Some("Bash"))],
);
assert_eq!(warnings, Vec::<String>::new());
assert_eq!(handlers, Vec::<ConfiguredHandler>::new());
assert_eq!(hook_entries.len(), 1);
assert_eq!(hook_entries[0].trust_status, HookTrustStatus::Untrusted);
assert_eq!(hook_entries[0].enabled, false);
}
#[test]
fn pre_tool_use_treats_star_matcher_as_match_all() {
let mut handlers = Vec::new();
+2
View File
@@ -106,6 +106,7 @@ pub(crate) struct ClaudeHooksEngine {
impl ClaudeHooksEngine {
pub(crate) fn new(
enabled: bool,
bypass_hook_trust: bool,
config_layer_stack: Option<&ConfigLayerStack>,
plugin_hook_sources: Vec<PluginHookSource>,
plugin_hook_load_warnings: Vec<String>,
@@ -125,6 +126,7 @@ impl ClaudeHooksEngine {
config_layer_stack,
plugin_hook_sources,
plugin_hook_load_warnings,
bypass_hook_trust,
);
Self {
handlers: discovered.handlers,
+54 -12
View File
@@ -196,6 +196,7 @@ with Path(r"{log_path}").open("a", encoding="utf-8") as handle:
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
@@ -211,6 +212,7 @@ with Path(r"{log_path}").open("a", encoding="utf-8") as handle:
let listed = crate::list_hooks(crate::HooksConfig {
legacy_notify_argv: None,
feature_enabled: true,
bypass_hook_trust: false,
config_layer_stack: Some(config_layer_stack.clone()),
plugin_hook_sources: Vec::new(),
plugin_hook_load_warnings: Vec::new(),
@@ -295,6 +297,7 @@ async fn requirements_managed_hooks_execute_windows_command_override() {
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
@@ -372,6 +375,7 @@ fn unknown_requirement_source_hooks_stay_managed() {
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
@@ -383,8 +387,12 @@ fn unknown_requirement_source_hooks_stay_managed() {
assert_eq!(engine.handlers.len(), 1);
assert_eq!(engine.handlers[0].source, HookSource::Unknown);
let discovered =
super::discovery::discover_handlers(Some(&config_layer_stack), Vec::new(), Vec::new());
let discovered = super::discovery::discover_handlers(
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
/*bypass_hook_trust*/ false,
);
assert_eq!(discovered.hook_entries.len(), 1);
assert_eq!(discovered.hook_entries[0].source, HookSource::Unknown);
assert_eq!(discovered.hook_entries[0].enabled, true);
@@ -446,6 +454,7 @@ fn user_disablement_filters_non_managed_hooks_but_not_managed_hooks() {
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
@@ -457,8 +466,12 @@ fn user_disablement_filters_non_managed_hooks_but_not_managed_hooks() {
assert_eq!(engine.handlers.len(), 1);
assert_eq!(engine.handlers[0].source, HookSource::CloudRequirements);
let discovered =
super::discovery::discover_handlers(Some(&config_layer_stack), Vec::new(), Vec::new());
let discovered = super::discovery::discover_handlers(
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
/*bypass_hook_trust*/ false,
);
assert_eq!(discovered.hook_entries.len(), 2);
assert_eq!(discovered.hook_entries[0].key, managed_disabled_key);
assert_eq!(discovered.hook_entries[0].enabled, true);
@@ -503,6 +516,7 @@ fn user_disablement_does_not_filter_managed_layer_hooks() {
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
@@ -517,8 +531,12 @@ fn user_disablement_does_not_filter_managed_layer_hooks() {
engine.handlers[0].source,
HookSource::LegacyManagedConfigFile
);
let discovered =
super::discovery::discover_handlers(Some(&config_layer_stack), Vec::new(), Vec::new());
let discovered = super::discovery::discover_handlers(
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
/*bypass_hook_trust*/ false,
);
assert_eq!(discovered.hook_entries.len(), 1);
assert_eq!(discovered.hook_entries[0].key, managed_key);
assert_eq!(discovered.hook_entries[0].enabled, true);
@@ -586,6 +604,7 @@ fn trusted_plugin_hook_stack(
/*config_layer_stack*/ None,
plugin_hook_sources.to_vec(),
Vec::new(),
/*bypass_hook_trust*/ false,
);
let state = discovered
.hook_entries
@@ -655,6 +674,7 @@ fn requirements_managed_hooks_load_when_managed_dir_is_missing() {
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
@@ -706,6 +726,7 @@ fn allow_managed_hooks_only_false_keeps_unmanaged_hooks() {
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
@@ -717,8 +738,12 @@ fn allow_managed_hooks_only_false_keeps_unmanaged_hooks() {
assert!(engine.warnings().is_empty());
assert!(engine.handlers.is_empty());
let discovered =
super::discovery::discover_handlers(Some(&config_layer_stack), Vec::new(), Vec::new());
let discovered = super::discovery::discover_handlers(
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
/*bypass_hook_trust*/ false,
);
assert_eq!(discovered.hook_entries.len(), 1);
assert!(!discovered.hook_entries[0].is_managed);
assert_eq!(
@@ -752,6 +777,7 @@ fn allow_managed_hooks_only_in_config_toml_does_not_enable_policy() {
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
@@ -763,8 +789,12 @@ fn allow_managed_hooks_only_in_config_toml_does_not_enable_policy() {
assert!(engine.warnings().is_empty());
assert!(engine.handlers.is_empty());
let discovered =
super::discovery::discover_handlers(Some(&config_layer_stack), Vec::new(), Vec::new());
let discovered = super::discovery::discover_handlers(
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
/*bypass_hook_trust*/ false,
);
assert_eq!(discovered.hook_entries.len(), 1);
assert!(!discovered.hook_entries[0].is_managed);
assert_eq!(
@@ -814,6 +844,7 @@ fn allow_managed_hooks_only_skips_unmanaged_json_and_toml_hooks() {
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
@@ -852,6 +883,7 @@ fn allow_managed_hooks_only_skips_unmanaged_plugin_hooks() {
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
Some(&config_layer_stack),
plugin_hook_sources,
Vec::new(),
@@ -923,6 +955,7 @@ fn allow_managed_hooks_only_keeps_managed_requirement_and_config_layer_hooks() {
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
@@ -947,8 +980,12 @@ fn allow_managed_hooks_only_keeps_managed_requirement_and_config_layer_hooks() {
"python3 /tmp/legacy-mdm-hook.py",
]
);
let discovered =
super::discovery::discover_handlers(Some(&config_layer_stack), Vec::new(), Vec::new());
let discovered = super::discovery::discover_handlers(
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
/*bypass_hook_trust*/ false,
);
assert!(discovered.hook_entries.iter().all(|entry| entry.is_managed));
}
@@ -1028,6 +1065,7 @@ fn discovers_hooks_from_json_and_toml_in_the_same_layer() {
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
Some(&config_layer_stack),
Vec::new(),
Vec::new(),
@@ -1119,6 +1157,7 @@ print(json.dumps({
);
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
Some(&config_layer_stack),
plugin_hook_sources.clone(),
Vec::new(),
@@ -1146,6 +1185,7 @@ print(json.dumps({
let listed = crate::list_hooks(crate::HooksConfig {
legacy_notify_argv: None,
feature_enabled: true,
bypass_hook_trust: false,
config_layer_stack: None,
plugin_hook_sources,
plugin_hook_load_warnings: Vec::new(),
@@ -1229,6 +1269,7 @@ fn plugin_hook_sources_expand_plugin_placeholders() {
);
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
Some(&config_layer_stack),
plugin_hook_sources,
Vec::new(),
@@ -1272,6 +1313,7 @@ fn plugin_hook_sources_expand_plugin_placeholders() {
fn plugin_hook_load_warnings_are_startup_warnings() {
let engine = ClaudeHooksEngine::new(
/*enabled*/ true,
/*bypass_hook_trust*/ false,
/*config_layer_stack*/ None,
Vec::new(),
vec!["failed plugin hook".to_string()],
+3
View File
@@ -30,6 +30,7 @@ use crate::types::HookResponse;
pub struct HooksConfig {
pub legacy_notify_argv: Option<Vec<String>>,
pub feature_enabled: bool,
pub bypass_hook_trust: bool,
pub config_layer_stack: Option<ConfigLayerStack>,
pub plugin_hook_sources: Vec<PluginHookSource>,
pub plugin_hook_load_warnings: Vec<String>,
@@ -65,6 +66,7 @@ impl Hooks {
.collect();
let engine = ClaudeHooksEngine::new(
config.feature_enabled,
config.bypass_hook_trust,
config.config_layer_stack.as_ref(),
config.plugin_hook_sources,
config.plugin_hook_load_warnings,
@@ -212,6 +214,7 @@ pub fn list_hooks(config: HooksConfig) -> HookListOutcome {
config.config_layer_stack.as_ref(),
config.plugin_hook_sources,
config.plugin_hook_load_warnings,
config.bypass_hook_trust,
);
HookListOutcome {
hooks: discovered.hook_entries,
@@ -163,6 +163,7 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
let mut config = Config {
config_layer_stack: ConfigLayerStack::default(),
startup_warnings: Vec::new(),
bypass_hook_trust: false,
model,
service_tier: None,
review_model: None,
+12
View File
@@ -304,6 +304,18 @@ async fn ignore_same_thread_resume_allows_reattaching_displayed_inactive_thread(
assert!(app.transcript_cells.is_empty());
}
#[test]
fn bypass_hook_trust_startup_warning_snapshot() {
let rendered = lines_to_single_string(
&history_cell::new_warning_event(
"`--dangerously-bypass-hook-trust` is enabled. Enabled hooks may run without review for this invocation."
.to_string(),
)
.display_lines(/*width*/ 80),
);
assert_app_snapshot!("bypass_hook_trust_startup_warning", rendered);
}
#[tokio::test]
async fn enqueue_primary_thread_session_replays_buffered_approval_after_attach() -> Result<()> {
let (mut app, mut app_event_rx, _op_rx) = make_test_app_with_channels().await;
+1
View File
@@ -928,6 +928,7 @@ pub async fn run_main(
codex_linux_sandbox_exe: arg0_paths.codex_linux_sandbox_exe.clone(),
main_execve_wrapper_exe: arg0_paths.main_execve_wrapper_exe.clone(),
show_raw_agent_reasoning: cli.oss.then_some(true),
bypass_hook_trust: cli.bypass_hook_trust.then_some(true),
additional_writable_roots: additional_dirs,
..Default::default()
};
@@ -0,0 +1,6 @@
---
source: tui/src/app/tests.rs
expression: rendered
---
⚠ `--dangerously-bypass-hook-trust` is enabled. Enabled hooks may run without
review for this invocation.
+14
View File
@@ -47,6 +47,11 @@ pub struct SharedCliOptions {
)]
pub dangerously_bypass_approvals_and_sandbox: bool,
/// Run enabled hooks without requiring persisted hook trust for this invocation.
/// DANGEROUS. Intended only for automation that already vets hook sources.
#[arg(long = "dangerously-bypass-hook-trust", default_value_t = false)]
pub bypass_hook_trust: bool,
/// Tell the agent to use the specified directory as its working root.
#[clap(long = "cd", short = 'C', value_name = "DIR")]
pub cwd: Option<PathBuf>,
@@ -68,6 +73,7 @@ impl SharedCliOptions {
config_profile,
sandbox_mode,
dangerously_bypass_approvals_and_sandbox,
bypass_hook_trust,
cwd,
add_dir,
} = self;
@@ -79,6 +85,7 @@ impl SharedCliOptions {
config_profile: root_config_profile,
sandbox_mode: root_sandbox_mode,
dangerously_bypass_approvals_and_sandbox: root_dangerously_bypass_approvals_and_sandbox,
bypass_hook_trust: root_bypass_hook_trust,
cwd: root_cwd,
add_dir: root_add_dir,
} = root;
@@ -102,6 +109,9 @@ impl SharedCliOptions {
*dangerously_bypass_approvals_and_sandbox =
*root_dangerously_bypass_approvals_and_sandbox;
}
if !*bypass_hook_trust {
*bypass_hook_trust = *root_bypass_hook_trust;
}
if cwd.is_none() {
cwd.clone_from(root_cwd);
}
@@ -128,6 +138,7 @@ impl SharedCliOptions {
config_profile,
sandbox_mode,
dangerously_bypass_approvals_and_sandbox,
bypass_hook_trust,
cwd,
add_dir,
} = subcommand;
@@ -149,6 +160,9 @@ impl SharedCliOptions {
self.dangerously_bypass_approvals_and_sandbox =
dangerously_bypass_approvals_and_sandbox;
}
if bypass_hook_trust {
self.bypass_hook_trust = true;
}
if let Some(cwd) = cwd {
self.cwd = Some(cwd);
}