use super::fixtures::*;
use crate::config::ToolOutputBound;
use crate::prompt::ExecError;
use crate::prompt::run;
use crate::prompt::tool::{ToolCall, ToolExecutor, ToolOutcome};
use brazen::FinishReason;
use serde_json::{Value, json};
use std::path::{Path, PathBuf};
use std::sync::atomic::AtomicBool;
const EDITED_SOUL: &str = "the edited soul";
const EDITED_PROVIDERS: &str = r#"
roles:
worker:
provider: anthropic
model: claude-opus-5
tools: [bash, read_file]
"#;
struct EditsConfigMidExchange {
inner: StubToolExecutor,
workspace: PathBuf,
}
impl ToolExecutor for EditsConfigMidExchange {
fn execute(
&self,
call: ToolCall<'_>,
step_dir: &Path,
stop: &AtomicBool,
bound: Option<ToolOutputBound>,
) -> Result<ToolOutcome, ExecError> {
std::fs::write(self.workspace.join("souls/worker.md"), EDITED_SOUL).unwrap();
std::fs::write(self.workspace.join("providers.yaml"), EDITED_PROVIDERS).unwrap();
self.inner.execute(call, step_dir, stop, bound)
}
}
fn system_text(request: &Value) -> String {
request["system"][0]["text"].as_str().unwrap().to_string()
}
fn request(repo: &Path, step: &str) -> Value {
let path = repo.join(format!("steps/ct-1-deadbeef/{step}/request.json"));
serde_json::from_slice(&std::fs::read(path).unwrap()).unwrap()
}
#[test]
fn a_config_edit_between_two_steps_governs_the_second_one() {
let repo = scaffold_repo(VALID_PER_REPO_PROVIDERS_YAML, Some("the original soul"));
let harness = scaffold_harness_root();
let tool_use = stream_of(
FinishReason::ToolUse,
&[Block::ToolUse {
id: "toolu_01",
name: "bash",
input: json!({ "cmd": "ls" }),
}],
);
let done = stream_of(FinishReason::Stop, &[Block::Text("done")]);
let adapter = StubAdapter::scripted([
StubAdapter::reply_ok(&version_line()),
StubAdapter::reply_ok(&tool_use),
StubAdapter::reply_ok(&version_line()),
StubAdapter::reply_ok(&done),
]);
let git = StubGit::ok();
let (clock, id) = (FixedClock::default(), FixedIdGen);
let sleeper = StubSleeper::default();
let unused = StubToolExecutor::ok();
let editor = EditsConfigMidExchange {
inner: StubToolExecutor::with_reply("bash", "files: a b"),
workspace: repo.path().to_path_buf(),
};
let mut deps = valid_deps(
&adapter,
&sleeper,
&git,
&clock,
&id,
&unused,
harness.path(),
);
deps.tool_executor = &editor;
run(
repo.path(),
"list files",
None,
None,
None,
crate::prompt::PinnedDocs::none(),
None,
&deps,
)
.unwrap();
let first = request(repo.path(), "001");
assert_eq!(first["model"], "claude-sonnet-5");
assert!(
system_text(&first).contains("the original soul"),
"{first:#}"
);
let second = request(repo.path(), "002");
assert_eq!(second["model"], "claude-opus-5");
let system = system_text(&second);
assert!(system.contains(EDITED_SOUL), "{second:#}");
assert!(!system.contains("the original soul"), "{second:#}");
let worktree = worktree_path(repo.path());
assert!(worktree.join("messages/002-claude-sonnet-5.json").is_file());
assert!(worktree.join("messages/004-claude-opus-5.json").is_file());
}