Rule syntax error on individual file invalidates all the `.rules` files.

Resolved 💬 2 comments Opened Feb 12, 2026 by polyrand Closed Feb 13, 2026

What issue are you seeing?

Syntax errors in .rules file are not reported, and an error in a single file invalidates the rest of the .rules files.

What steps can reproduce the bug?

  1. Create two rules files in ~/.codex/rules/:
# ~/.codex/rules/valid.rules
prefix_rule(
    pattern = ["subcommand-foo-bar"],
    decision = "allow",
    match = ["subcommand-foo-bar"],
)
# ~/.codex/rules/broken.rules
prefix_rule(
    pattern = ["tmux capture-pane"],
    decision = "allow",
    match = ["tmux capture-pane -p"],
)

The second file has a bug: "tmux capture-pane" is a single token with a space, so the match validation fails because shlex::split("tmux capture-pane -p") produces ["tmux", "capture-pane", "-p"] where token 0 "tmux" != "tmux capture-pane".

  1. Run codex --ask-for-approval untrusted
  2. Ask the model to run echo hello
  3. Codex prompts for approval even though valid.rules should allow it

What is the expected behavior?

  • Valid rules files should still load even if other files have errors. Either skip the broken file and load the rest, or parse all files independently and merge the results.
  • A visible warning should appear in the TUI when rules fail to parse, so the user knows their policy is not active.

Additional information

There are two related issues with how Codex handles .rules files in ~/.codex/rules/:

1. A syntax error in one .rules file invalidates all other .rules files

load_exec_policy in codex-rs/core/src/exec_policy.rs uses a single PolicyParser that iterates over all .rules files sequentially. If any file fails to parse, the ? operator aborts the loop and load_exec_policy_with_warning replaces the entire policy with Policy::empty():

let mut parser = PolicyParser::new();
for policy_path in &policy_paths {
    let contents = fs::read_to_string(policy_path).await...?;
    parser.parse(&identifier, &contents).map_err(...)? ;  // <-- aborts on first error
}
async fn load_exec_policy_with_warning(...) {
    match load_exec_policy(config_stack).await {
        Ok(policy) => Ok((policy, None)),
        Err(err @ ExecPolicyError::ParsePolicy { .. }) => Ok((Policy::empty(), Some(err))),
        // ...
    }
}

This means a typo in tmux-capture-pane.rules silently disables a completely valid pg-eai-ro.rules in the same directory.

2. Parse errors are not surfaced to the user in the TUI

The only indication of a failed rules load is a tracing::warn! written to ~/.codex/log/codex-tui.log:

if let Some(err) = warning.as_ref() {
    tracing::warn!("failed to parse rules: {err}");
}

No message is shown in the TUI. The user has no way to know their rules are not loaded without enabling debug logging (RUST_LOG=codex_core=debug) and tailing the log file manually.

Workaround

Enable debug logging to find the broken file:

RUST_LOG=codex_core=debug codex
# In another terminal:
tail -f ~/.codex/log/codex-tui.log | grep -i rule

Fix or remove the broken .rules file. All other rules will load again.

Context

https://github.com/openai/codex/issues/10321#issuecomment-3890312827

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗