Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/isolation.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ The hosts in the agents' `plugin.json` are a starting point: check them against

A pane's `~/.pi/agent/*.lock` is writable from every This-folder-only pane: one session can hold another pi session's lock, which only delays that session until the lock is deleted.

**This folder only** also keeps one session from planting code or instructions for another. Inside the places it may write, these stay read-only: shell, git and editor startup files, `~/.local/bin` and other folders ahead on `PATH`, agents' settings and memory, Claude Code's task list, Codex's shared databases, Qwen Code's workflow scripts, what agents source or run later from their own folders, and the caches package managers run code from (pnpm, uv, deno, Go, Playwright, Poetry, Yarn, node-gyp, pip, Cypress, SwiftPM, Copilot). When the pane's folder is your home folder, its dot files, `~/Library` and `~/bin` stay read-only too, except `~/Library/Caches` and the agents' session folders; choose a project folder for a real limit. The **Isolation** window lists what stops working.
**This folder only** also keeps one session from planting code or instructions for another. Inside the places it may write, these stay read-only: shell, git and editor startup files, `~/.local/bin` and other folders ahead on `PATH`, agents' settings and memory, Claude Code's task list, Codex's shared databases, Qwen Code's workflow scripts, what agents source or run later from their own folders, and the known caches whose contents run later (pnpm, uv, deno, Go, Playwright, Poetry, Yarn, node-gyp, pip, Cypress, SwiftPM, Copilot, the system Python's bytecode, Homebrew, ccache, sccache, esbuild, Bazelisk, Coursier); another cache under `~/Library/Caches` stays writable. Seatbelt sees a folder moved in whole as one being made, so a missing `~/.<agent>/projects` folder, or a project folder in it an agent has not made yet, can still be put in place with memory or Qwen Code's workflow scripts inside. When the pane's folder is your home folder, its dot files, `~/Library` and `~/bin` stay read-only too, except `~/Library/Caches` and the agents' session folders; choose a project folder for a real limit. The **Isolation** window lists what stops working.

Local sockets: with Network **Open**, a This-folder-only pane reaches no ssh agent and no Docker, colima, OrbStack, Rancher Desktop, lima or podman socket, so `git` over ssh and `docker` do not work there. With **Only these providers** or **None**, a pane reaches no Unix socket but nerd's message socket.

Expand Down
13 changes: 13 additions & 0 deletions src/hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,7 @@ fn install_one(a: &Manifest, home: &Path) -> std::io::Result<()> {
if let Some(dir) = config.parent() {
std::fs::create_dir_all(dir)?;
}
crate::isolation::make_agent_folders(a.data_paths(home), home);
if a.shape() == Some(Shape::Plugin) {
return std::fs::write(&config, plugin_for(a, &state_root(home)));
}
Expand Down Expand Up @@ -1861,6 +1862,18 @@ pub(crate) mod tests {
out
}

#[test]
fn install_makes_the_agent_folder_a_pane_could_move_in_whole() {
let home = std::env::temp_dir().join(format!("nerd-home-folders-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&home);
std::fs::create_dir_all(&home).unwrap();
let gemini = crate::agents::builtins().into_iter().find(|m| m.name == "gemini").unwrap();
install_one(&gemini, &home).unwrap();
assert!(home.join(".gemini/tmp").is_dir(), "Gemini's tmp holds its tools folder");
assert!(!home.join(".gemini/projects.json").exists(), "only folders, only those");
let _ = std::fs::remove_dir_all(&home);
}

fn ours(v: &Value) -> usize {
v["hooks"].as_object().map_or(0, |h| h.values().filter_map(Value::as_array).flatten().filter(|e| is_ours(e)).count())
}
Expand Down
Loading
Loading