mirror of
https://github.com/openai/codex.git
synced 2026-06-01 19:02:59 +00:00
Report syntax errors in rules file (#11686)
Currently, if there are syntax errors detected in the starlark rules file, the entire policy is silently ignored by the CLI. The app server correctly emits a message that can be displayed in a GUI. This PR changes the CLI (both the TUI and non-interactive exec) to fail when the rules file can't be parsed. It then prints out an error message and exits with a non-zero exit code. This is consistent with the handling of errors in the config file. This addresses #11603
This commit is contained in:
@@ -265,6 +265,77 @@ pub async fn check_execpolicy_for_warnings(
|
||||
Ok(warning)
|
||||
}
|
||||
|
||||
fn exec_policy_message_for_display(source: &codex_execpolicy::Error) -> String {
|
||||
let message = source.to_string();
|
||||
if let Some(line) = message
|
||||
.lines()
|
||||
.find(|line| line.trim_start().starts_with("error: "))
|
||||
{
|
||||
return line.to_owned();
|
||||
}
|
||||
if let Some(first_line) = message.lines().next()
|
||||
&& let Some((_, detail)) = first_line.rsplit_once(": starlark error: ")
|
||||
{
|
||||
return detail.trim().to_string();
|
||||
}
|
||||
|
||||
message
|
||||
.lines()
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn parse_starlark_line_from_message(message: &str) -> Option<(PathBuf, usize)> {
|
||||
let first_line = message.lines().next()?.trim();
|
||||
let (path_and_position, _) = first_line.rsplit_once(": starlark error:")?;
|
||||
|
||||
let mut parts = path_and_position.rsplitn(3, ':');
|
||||
let _column = parts.next()?.parse::<usize>().ok()?;
|
||||
let line = parts.next()?.parse::<usize>().ok()?;
|
||||
let path = PathBuf::from(parts.next()?);
|
||||
|
||||
if line == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((path, line))
|
||||
}
|
||||
|
||||
pub fn format_exec_policy_error_with_source(error: &ExecPolicyError) -> String {
|
||||
match error {
|
||||
ExecPolicyError::ParsePolicy { path, source } => {
|
||||
let rendered_source = source.to_string();
|
||||
let structured_location = source
|
||||
.location()
|
||||
.map(|location| (PathBuf::from(location.path), location.range.start.line));
|
||||
let parsed_location = parse_starlark_line_from_message(&rendered_source);
|
||||
let location = match (structured_location, parsed_location) {
|
||||
(Some((_, 1)), Some((parsed_path, parsed_line))) if parsed_line > 1 => {
|
||||
Some((parsed_path, parsed_line))
|
||||
}
|
||||
(Some(structured), _) => Some(structured),
|
||||
(None, parsed) => parsed,
|
||||
};
|
||||
let message = exec_policy_message_for_display(source);
|
||||
match location {
|
||||
Some((path, line)) => {
|
||||
format!(
|
||||
"{}:{}: {} (problem is on or around line {})",
|
||||
path.display(),
|
||||
line,
|
||||
message,
|
||||
line
|
||||
)
|
||||
}
|
||||
None => format!("{path}: {message}"),
|
||||
}
|
||||
}
|
||||
_ => error.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_exec_policy_with_warning(
|
||||
config_stack: &ConfigLayerStack,
|
||||
) -> Result<(Policy, Option<ExecPolicyError>), ExecPolicyError> {
|
||||
@@ -679,6 +750,51 @@ mod tests {
|
||||
assert!(files.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn format_exec_policy_error_with_source_renders_range() {
|
||||
let temp_dir = tempdir().expect("create temp dir");
|
||||
let config_stack = config_stack_for_dot_codex_folder(temp_dir.path());
|
||||
let policy_dir = temp_dir.path().join(RULES_DIR_NAME);
|
||||
fs::create_dir_all(&policy_dir).expect("create policy dir");
|
||||
let broken_path = policy_dir.join("broken.rules");
|
||||
fs::write(
|
||||
&broken_path,
|
||||
r#"prefix_rule(
|
||||
pattern = ["tmux capture-pane"],
|
||||
decision = "allow",
|
||||
match = ["tmux capture-pane -p"],
|
||||
)"#,
|
||||
)
|
||||
.expect("write broken policy file");
|
||||
|
||||
let err = load_exec_policy(&config_stack)
|
||||
.await
|
||||
.expect_err("expected parse error");
|
||||
let rendered = format_exec_policy_error_with_source(&err);
|
||||
|
||||
assert!(rendered.contains("broken.rules:1:"));
|
||||
assert!(rendered.contains("on or around line 1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_starlark_line_from_message_extracts_path_and_line() {
|
||||
let parsed = parse_starlark_line_from_message(
|
||||
"/tmp/default.rules:143:1: starlark error: error: Parse error: unexpected new line",
|
||||
)
|
||||
.expect("parse should succeed");
|
||||
|
||||
assert_eq!(parsed.0, PathBuf::from("/tmp/default.rules"));
|
||||
assert_eq!(parsed.1, 143);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_starlark_line_from_message_rejects_zero_line() {
|
||||
let parsed = parse_starlark_line_from_message(
|
||||
"/tmp/default.rules:0:1: starlark error: error: Parse error: unexpected new line",
|
||||
);
|
||||
assert_eq!(parsed, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn loads_policies_from_policy_subdirectory() {
|
||||
let temp_dir = tempdir().expect("create temp dir");
|
||||
|
||||
@@ -144,6 +144,7 @@ pub use apply_patch::CODEX_APPLY_PATCH_ARG1;
|
||||
pub use client::X_CODEX_TURN_METADATA_HEADER;
|
||||
pub use exec_policy::ExecPolicyError;
|
||||
pub use exec_policy::check_execpolicy_for_warnings;
|
||||
pub use exec_policy::format_exec_policy_error_with_source;
|
||||
pub use exec_policy::load_exec_policy;
|
||||
pub use file_watcher::FileWatcherEvent;
|
||||
pub use safety::get_platform_sandbox;
|
||||
|
||||
Reference in New Issue
Block a user