diff --git a/test/hermetic-wiring.test.ts b/test/hermetic-wiring.test.ts index 658344cfe..87fe13523 100644 --- a/test/hermetic-wiring.test.ts +++ b/test/hermetic-wiring.test.ts @@ -125,10 +125,21 @@ describe('hermetic wiring tripwire', () => { expect(configDir.startsWith(runRoot + path.sep)).toBe(true); expect(configDir.startsWith(operatorClaude)).toBe(false); const skillsDir = path.join(configDir, 'skills'); + const repoRootReal = fs.realpathSync(ROOT) + path.sep; for (const entry of fs.readdirSync(skillsDir)) { const target = fs.readlinkSync(path.join(skillsDir, entry, 'SKILL.md')); - expect(target.startsWith(operatorClaude), `${entry}: symlink escapes to ${target}`).toBe(false); - expect(fs.realpathSync(target).startsWith(fs.realpathSync(ROOT) + path.sep), `${entry}: symlink outside repo: ${target}`).toBe(true); + const resolved = fs.realpathSync(target); + // Targets inside the live repo checkout are the blessed edge — exempt + // them BEFORE the operator-~/.claude ban. On the default global-git + // install the repo itself lives at ~/.claude/skills/gstack, so every + // CORRECT symlink carries the operatorClaude prefix and an unexempted + // ban can never pass (regression 2026-08-15: pristine v1.64.1.0 fails + // this test in any worktree under ~/.claude/skills/ and passes + // elsewhere — realpath both sides so a symlinked HOME can't dodge it). + if (!resolved.startsWith(repoRootReal)) { + expect(resolved.startsWith(operatorClaude), `${entry}: symlink escapes to ${target}`).toBe(false); + } + expect(resolved.startsWith(repoRootReal), `${entry}: symlink outside repo: ${target}`).toBe(true); } }); });