mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-07 08:27:12 +00:00
Exempt /tmp/ paths from read-before-write guard
The ReadBeforeWriteSandbox was blocking engine writes to /tmp/arc-commit-msg on the second git checkpoint because the file already existed from the first checkpoint. Temp files don't need source-file protection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4e84560bed
commit
3ff0494caf
3 changed files with 39 additions and 24 deletions
|
|
@ -61,6 +61,10 @@ impl ReadBeforeWriteSandbox {
|
|||
}
|
||||
|
||||
async fn guard_write(&self, path: &str) -> Result<(), String> {
|
||||
let normalized = self.normalize_path(path);
|
||||
if normalized.starts_with("/tmp/") {
|
||||
return Ok(());
|
||||
}
|
||||
let exists = self.inner.file_exists(path).await?;
|
||||
if exists && !self.has_read(path) {
|
||||
warn!(path = %path, "Write blocked: file not read by agent");
|
||||
|
|
@ -99,13 +103,18 @@ mod tests {
|
|||
use crate::test_support::MockSandbox;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn mock_with_files(files: HashMap<String, String>) -> MockSandbox {
|
||||
MockSandbox {
|
||||
files,
|
||||
working_dir: "/work",
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
// Cycle 1: write to existing unread file → error
|
||||
#[tokio::test]
|
||||
async fn write_to_existing_unread_file_returns_error() {
|
||||
let mock = MockSandbox {
|
||||
files: HashMap::from([("a.ts".into(), "content".into())]),
|
||||
..Default::default()
|
||||
};
|
||||
let mock = mock_with_files(HashMap::from([("a.ts".into(), "content".into())]));
|
||||
let env = ReadBeforeWriteSandbox::new(Arc::new(mock));
|
||||
|
||||
let result = env.write_file("a.ts", "new content").await;
|
||||
|
|
@ -119,7 +128,7 @@ mod tests {
|
|||
// Cycle 2: write to non-existent file → success
|
||||
#[tokio::test]
|
||||
async fn write_to_nonexistent_file_succeeds() {
|
||||
let mock = MockSandbox::default();
|
||||
let mock = mock_with_files(HashMap::new());
|
||||
let env = ReadBeforeWriteSandbox::new(Arc::new(mock));
|
||||
|
||||
let result = env.write_file("new.ts", "content").await;
|
||||
|
|
@ -130,10 +139,7 @@ mod tests {
|
|||
// Cycle 3: mark_agent_read then write → success
|
||||
#[tokio::test]
|
||||
async fn read_then_write_succeeds() {
|
||||
let mock = MockSandbox {
|
||||
files: HashMap::from([("a.ts".into(), "content".into())]),
|
||||
..Default::default()
|
||||
};
|
||||
let mock = mock_with_files(HashMap::from([("a.ts".into(), "content".into())]));
|
||||
let env = ReadBeforeWriteSandbox::new(Arc::new(mock));
|
||||
|
||||
env.mark_agent_read("a.ts");
|
||||
|
|
@ -145,10 +151,7 @@ mod tests {
|
|||
// Cycle 4: read_file alone does NOT satisfy guard
|
||||
#[tokio::test]
|
||||
async fn read_file_alone_does_not_satisfy_guard() {
|
||||
let mock = MockSandbox {
|
||||
files: HashMap::from([("a.ts".into(), "content".into())]),
|
||||
..Default::default()
|
||||
};
|
||||
let mock = mock_with_files(HashMap::from([("a.ts".into(), "content".into())]));
|
||||
let env = ReadBeforeWriteSandbox::new(Arc::new(mock));
|
||||
|
||||
env.read_file("a.ts", None, None).await.unwrap();
|
||||
|
|
@ -163,6 +166,7 @@ mod tests {
|
|||
let mock = MockSandbox {
|
||||
files: HashMap::from([("b.ts".into(), "content".into())]),
|
||||
grep_results: vec!["b.ts:1:content".into()],
|
||||
working_dir: "/work",
|
||||
..Default::default()
|
||||
};
|
||||
let env = ReadBeforeWriteSandbox::new(Arc::new(mock));
|
||||
|
|
@ -181,6 +185,7 @@ mod tests {
|
|||
let mock = MockSandbox {
|
||||
files: HashMap::from([("b.ts".into(), "content".into())]),
|
||||
grep_results: vec!["b.ts:1:content".into()],
|
||||
working_dir: "/work",
|
||||
..Default::default()
|
||||
};
|
||||
let env = ReadBeforeWriteSandbox::new(Arc::new(mock));
|
||||
|
|
@ -197,6 +202,7 @@ mod tests {
|
|||
let mock = MockSandbox {
|
||||
files: HashMap::from([("c.ts".into(), "content".into())]),
|
||||
glob_results: vec!["c.ts".into()],
|
||||
working_dir: "/work",
|
||||
..Default::default()
|
||||
};
|
||||
let env = ReadBeforeWriteSandbox::new(Arc::new(mock));
|
||||
|
|
@ -229,10 +235,7 @@ mod tests {
|
|||
// Cycle 9: delete unread file → error
|
||||
#[tokio::test]
|
||||
async fn delete_unread_file_returns_error() {
|
||||
let mock = MockSandbox {
|
||||
files: HashMap::from([("d.ts".into(), "content".into())]),
|
||||
..Default::default()
|
||||
};
|
||||
let mock = mock_with_files(HashMap::from([("d.ts".into(), "content".into())]));
|
||||
let env = ReadBeforeWriteSandbox::new(Arc::new(mock));
|
||||
|
||||
let result = env.delete_file("d.ts").await;
|
||||
|
|
@ -243,10 +246,7 @@ mod tests {
|
|||
// Cycle 10: error message is actionable
|
||||
#[tokio::test]
|
||||
async fn error_message_is_actionable() {
|
||||
let mock = MockSandbox {
|
||||
files: HashMap::from([("main.rs".into(), "fn main() {}".into())]),
|
||||
..Default::default()
|
||||
};
|
||||
let mock = mock_with_files(HashMap::from([("main.rs".into(), "fn main() {}".into())]));
|
||||
let env = ReadBeforeWriteSandbox::new(Arc::new(mock));
|
||||
|
||||
let err = env.write_file("main.rs", "new").await.unwrap_err();
|
||||
|
|
@ -254,4 +254,19 @@ mod tests {
|
|||
assert!(err.contains("main.rs"));
|
||||
assert!(err.contains("read_file"));
|
||||
}
|
||||
|
||||
// Cycle 11: write to /tmp bypasses guard
|
||||
#[tokio::test]
|
||||
async fn write_to_tmp_bypasses_guard() {
|
||||
let mock = MockSandbox {
|
||||
files: HashMap::from([("/tmp/arc-commit-msg".into(), "old".into())]),
|
||||
working_dir: "/work",
|
||||
..Default::default()
|
||||
};
|
||||
let env = ReadBeforeWriteSandbox::new(Arc::new(mock));
|
||||
|
||||
let result = env.write_file("/tmp/arc-commit-msg", "new").await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,7 +449,7 @@ mod tests {
|
|||
fn mock_env_platform() {
|
||||
let env = MockSandbox::default();
|
||||
assert_eq!(env.platform(), "darwin");
|
||||
assert_eq!(env.working_directory(), "/tmp/test");
|
||||
assert_eq!(env.working_directory(), "/work");
|
||||
assert_eq!(env.os_version(), "Darwin 24.0.0");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ impl Default for MockSandbox {
|
|||
},
|
||||
grep_results: vec![],
|
||||
glob_results: vec![],
|
||||
working_dir: "/tmp/test",
|
||||
working_dir: "/work",
|
||||
platform_str: "darwin",
|
||||
os_version_str: "Darwin 24.0.0".into(),
|
||||
apply_read_offset_limit: false,
|
||||
|
|
@ -360,7 +360,7 @@ impl Sandbox for MutableMockSandbox {
|
|||
}
|
||||
|
||||
fn working_directory(&self) -> &'static str {
|
||||
"/tmp"
|
||||
"/work"
|
||||
}
|
||||
|
||||
fn platform(&self) -> &'static str {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue