From ab69ea113061c57676a1241ee39e332246b4078f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Tue, 20 Oct 2026 18:04:02 +0200 Subject: [PATCH] Check shared dirs: link, target, manifest, and removal on undeclare --- checks/default.nix | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/checks/default.nix b/checks/default.nix index 87cd52c082230470f77435693803f9180aa7e6be..39fd386a8731773d6b4d25ef023e85c159fbb7be 100644 --- a/checks/default.nix +++ b/checks/default.nix @@ -265,6 +265,49 @@ in [ "$(cat $s/CLAUDE.md)" = edited ] || fail "user file modified" ''; + # A shared directory is a managed link to the other launcher's copy. The copy + # is created on demand, so the link works whichever launcher starts first, and + # undeclaring the share removes the link but never the data behind it. + shared-dirs = + let + withShare = share: { + claude.enable = true; + launchers.claude-work.agent = "claude"; + launchers.claude-pers = { + agent = "claude"; + sharedDirs = share; + }; + }; + sharing = mkEnvironment { + inherit pkgs; + modules = [ + stubs + (withShare { projects = "claude-work"; }) + ]; + }; + alone = mkEnvironment { + inherit pkgs; + modules = [ + stubs + (withShare { }) + ]; + }; + work = "$HOME/.local/agent-environments/claude-work"; + pers = "$HOME/.local/agent-environments/claude-pers"; + in + runTest "shared-dirs" '' + ${sharing.launchers.claude-pers}/bin/claude-pers + [ -d ${work}/projects ] || fail "shared target not created" + [ "$(readlink ${pers}/projects)" = ${work}/projects ] || fail "link: $(readlink ${pers}/projects)" + grep -qx projects ${pers}/.agent-environments-managed || fail "link not in manifest" + echo data >${pers}/projects/session.jsonl + [ "$(cat ${work}/projects/session.jsonl)" = data ] || fail "write did not land in the shared dir" + + ${alone.launchers.claude-pers}/bin/claude-pers + [ ! -L ${pers}/projects ] || fail "undeclared share still linked" + [ "$(cat ${work}/projects/session.jsonl)" = data ] || fail "shared data removed with the link" + ''; + # The launcher's own bookkeeping paths must not write through symlinks into # files or directories outside the state dir. no-writes-through-symlinks = runTest "no-writes-through-symlinks" '' @@ -439,5 +482,21 @@ in src = skillSrc "x"; }).drvPath ).success; + # A shared dir must name another, existing launcher and a safe path component. + assert lib.all (share: fails [ + stubs + { + claude.enable = true; + launchers.x.agent = "claude"; + launchers.y = { + agent = "claude"; + sharedDirs = share; + }; + } + ]) [ + { projects = "nope"; } + { projects = "y"; } + { "../escape" = "x"; } + ]; pkgs.runCommand "agent-environments-eval-errors" { } "touch $out"; }