Skip to content

Commit 9c3b7e5

Browse files
concurrent adds
1 parent a1195b3 commit 9c3b7e5

1 file changed

Lines changed: 41 additions & 9 deletions

File tree

codex-rs/core/src/exec_policy.rs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ pub enum ExecPolicyUpdateError {
167167

168168
pub(crate) struct ExecPolicyManager {
169169
policy: ArcSwap<Policy>,
170+
update_lock: tokio::sync::Mutex<()>,
170171
}
171172

172173
pub(crate) struct ExecApprovalRequest<'a> {
@@ -181,6 +182,7 @@ impl ExecPolicyManager {
181182
pub(crate) fn new(policy: Arc<Policy>) -> Self {
182183
Self {
183184
policy: ArcSwap::from(policy),
185+
update_lock: tokio::sync::Mutex::new(()),
184186
}
185187
}
186188

@@ -285,6 +287,7 @@ impl ExecPolicyManager {
285287
codex_home: &Path,
286288
amendment: &ExecPolicyAmendment,
287289
) -> Result<(), ExecPolicyUpdateError> {
290+
let _update_guard = self.update_lock.lock().await;
288291
let policy_path = default_policy_path(codex_home);
289292
spawn_blocking({
290293
let policy_path = policy_path.clone();
@@ -298,14 +301,6 @@ impl ExecPolicyManager {
298301
source,
299302
})?;
300303

301-
self.apply_amendment_in_memory(amendment)?;
302-
Ok(())
303-
}
304-
305-
pub(crate) fn apply_amendment_in_memory(
306-
&self,
307-
amendment: &ExecPolicyAmendment,
308-
) -> Result<(), ExecPolicyUpdateError> {
309304
let current_policy = self.current();
310305
let match_options = MatchOptions {
311306
resolve_host_executables: true,
@@ -323,7 +318,7 @@ impl ExecPolicyManager {
323318
return Ok(());
324319
}
325320

326-
let mut updated_policy = self.current().as_ref().clone();
321+
let mut updated_policy = current_policy.as_ref().clone();
327322
updated_policy.add_prefix_rule(&amendment.command, Decision::Allow)?;
328323
self.policy.store(Arc::new(updated_policy));
329324
Ok(())
@@ -337,6 +332,7 @@ impl ExecPolicyManager {
337332
decision: Decision,
338333
justification: Option<String>,
339334
) -> Result<(), ExecPolicyUpdateError> {
335+
let _update_guard = self.update_lock.lock().await;
340336
let policy_path = default_policy_path(codex_home);
341337
let host = host.to_string();
342338
spawn_blocking({
@@ -1920,6 +1916,42 @@ prefix_rule(pattern=["git"], decision="prompt")
19201916
));
19211917
}
19221918

1919+
#[tokio::test]
1920+
async fn concurrent_execpolicy_amendments_preserve_both_rules() {
1921+
let codex_home = tempdir().expect("create temp dir");
1922+
let manager = Arc::new(ExecPolicyManager::default());
1923+
let echo = ExecPolicyAmendment::new(vec!["echo".to_string()]);
1924+
let cargo = ExecPolicyAmendment::new(vec!["cargo".to_string(), "test".to_string()]);
1925+
1926+
let (echo_result, cargo_result) = tokio::join!(
1927+
manager.append_amendment_and_update(codex_home.path(), &echo),
1928+
manager.append_amendment_and_update(codex_home.path(), &cargo),
1929+
);
1930+
echo_result.expect("update echo policy");
1931+
cargo_result.expect("update cargo policy");
1932+
1933+
let updated_policy = manager.current();
1934+
let echo_evaluation = updated_policy.check(&["echo".to_string()], &|_| Decision::Forbidden);
1935+
assert_eq!(echo_evaluation.decision, Decision::Allow);
1936+
assert!(echo_evaluation.matched_rules.iter().any(|rule_match| {
1937+
is_policy_match(rule_match) && rule_match.decision() == Decision::Allow
1938+
}));
1939+
1940+
let cargo_evaluation = updated_policy
1941+
.check(&["cargo".to_string(), "test".to_string()], &|_| {
1942+
Decision::Forbidden
1943+
});
1944+
assert_eq!(cargo_evaluation.decision, Decision::Allow);
1945+
assert!(cargo_evaluation.matched_rules.iter().any(|rule_match| {
1946+
is_policy_match(rule_match) && rule_match.decision() == Decision::Allow
1947+
}));
1948+
1949+
let contents = fs::read_to_string(default_policy_path(codex_home.path()))
1950+
.expect("policy file should have been created");
1951+
assert!(contents.contains(r#"prefix_rule(pattern=["echo"], decision="allow")"#));
1952+
assert!(contents.contains(r#"prefix_rule(pattern=["cargo", "test"], decision="allow")"#));
1953+
}
1954+
19231955
#[tokio::test]
19241956
async fn proposed_execpolicy_amendment_is_present_for_single_command_without_policy_match() {
19251957
let command = vec!["cargo".to_string(), "build".to_string()];

0 commit comments

Comments
 (0)