@@ -1466,8 +1466,16 @@ def test_explicit_root_beats_the_inferred_worktree(workspace: Path, monkeypatch:
14661466def test_explicit_root_outside_the_workspace_is_refused (
14671467 workspace : Path , tmp_path : Path , monkeypatch : pytest .MonkeyPatch
14681468) -> None :
1469- """A root that is neither this workspace, a worktree of it, nor an allowed dir."""
1469+ """A root that is neither this workspace, a worktree of it, nor an allowed dir.
1470+
1471+ The scratch allowance is dropped for the duration: pytest's basetemp lives
1472+ under /tmp on Linux, which is a real allowed write root, so a directory
1473+ placed there is legitimately in bounds and the refusal under test would
1474+ never fire. Removing the allowance is what makes `outside` outside on every
1475+ platform -- the companion test below pins the allowance itself.
1476+ """
14701477 wt = _repo_with_worktree (workspace )
1478+ monkeypatch .setattr (mcp_server , "_SCRATCH_EDIT_ROOTS" , ())
14711479 outside = tmp_path .parent / "outside-root"
14721480 outside .mkdir (parents = True , exist_ok = True )
14731481 (outside / "target.txt" ).write_text ("OUTSIDE\n " , encoding = "utf-8" )
@@ -1486,6 +1494,36 @@ def test_explicit_root_outside_the_workspace_is_refused(
14861494 assert (outside / "target.txt" ).read_text (encoding = "utf-8" ) == "OUTSIDE\n "
14871495
14881496
1497+ def test_explicit_root_under_a_scratch_root_is_allowed (
1498+ workspace : Path , tmp_path : Path , monkeypatch : pytest .MonkeyPatch
1499+ ) -> None :
1500+ """A scratch root is a real write root, so naming one as `root` is in bounds.
1501+
1502+ Writes under the scratch allowance predate `root=` -- staging a file before
1503+ moving it in is ordinary tool work -- so validating the argument must not
1504+ quietly narrow what was already writable.
1505+ """
1506+ wt = _repo_with_worktree (workspace )
1507+ # Outside the workspace, or the workspace root would allow it on its own and
1508+ # the scratch allowance under test would carry nothing.
1509+ scratch = tmp_path .parent / "scratch-root"
1510+ scratch .mkdir (parents = True , exist_ok = True )
1511+ (scratch / "target.txt" ).write_text ("SCRATCH\n " , encoding = "utf-8" )
1512+ monkeypatch .setattr (mcp_server , "_SCRATCH_EDIT_ROOTS" , (scratch ,))
1513+ monkeypatch .setattr (mcp_server , "_last_session_cwd" , str (wt ))
1514+
1515+ payload = _edit (
1516+ {
1517+ "post_edit_hooks" : False ,
1518+ "root" : str (scratch ),
1519+ "edits" : [{"file_path" : "target.txt" , "old_string" : "SCRATCH" , "new_string" : "EDITED" }],
1520+ }
1521+ )
1522+
1523+ assert "failed" not in payload , payload
1524+ assert (scratch / "target.txt" ).read_text (encoding = "utf-8" ) == "EDITED\n "
1525+
1526+
14891527def test_worktree_redirect_is_disclosed_to_the_model (workspace : Path , monkeypatch : pytest .MonkeyPatch ) -> None :
14901528 """The redirect is inferred from the last bash cwd, so it is never silent.
14911529
0 commit comments