From 5763bc84d8073d57ecb93b04f481658d7ada4d0c Mon Sep 17 00:00:00 2001 From: Brady Gaster Date: Mon, 29 Jun 2026 20:15:20 -0700 Subject: [PATCH 1/2] fix(test): guard observer symlink setup Handle restricted symlink creation in the observer symlink test so Windows and locked-down environments can continue the suite. Refs #1416 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/squad-observer.test.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/squad-observer.test.ts b/test/squad-observer.test.ts index 2abb6ecd3..c038fc1c8 100644 --- a/test/squad-observer.test.ts +++ b/test/squad-observer.test.ts @@ -60,6 +60,11 @@ function cleanupTmpDir() { } } +function isUnsupportedSymlinkError(error: unknown): boolean { + const code = (error as NodeJS.ErrnoException).code; + return code === 'EPERM' || code === 'EACCES' || code === 'ENOSYS'; +} + // --------------------------------------------------------------------------- // classifyFile tests // --------------------------------------------------------------------------- @@ -210,7 +215,13 @@ describe('SquadObserver', () => { const outsideDir = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-observer-outside-')); try { fs.writeFileSync(path.join(outsideDir, 'newest.md'), 'outside'); - fs.symlinkSync(outsideDir, path.join(squadDir, 'linked-outside'), 'dir'); + const symlinkPath = path.join(squadDir, 'linked-outside'); + try { + fs.symlinkSync(outsideDir, symlinkPath, 'dir'); + } catch (error) { + if (isUnsupportedSymlinkError(error)) return; + throw error; + } const olderFile = path.join(squadDir, 'team.md'); fs.writeFileSync(olderFile, 'inside'); From f8553a0cea060b9506c8c895badfe9424731f97c Mon Sep 17 00:00:00 2001 From: Brady Gaster Date: Mon, 29 Jun 2026 20:46:50 -0700 Subject: [PATCH 2/2] Address symlink test review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/squad-observer.test.ts | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/test/squad-observer.test.ts b/test/squad-observer.test.ts index c038fc1c8..673dae6cf 100644 --- a/test/squad-observer.test.ts +++ b/test/squad-observer.test.ts @@ -61,10 +61,30 @@ function cleanupTmpDir() { } function isUnsupportedSymlinkError(error: unknown): boolean { - const code = (error as NodeJS.ErrnoException).code; + const code = typeof error === 'object' && error !== null && 'code' in error + ? (error as NodeJS.ErrnoException).code + : undefined; return code === 'EPERM' || code === 'EACCES' || code === 'ENOSYS'; } +function canCreateSymlink(): boolean { + const probeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-observer-symlink-probe-')); + try { + const targetDir = path.join(probeDir, 'target'); + const symlinkPath = path.join(probeDir, 'link'); + fs.mkdirSync(targetDir); + fs.symlinkSync(targetDir, symlinkPath, 'dir'); + return true; + } catch (error) { + if (isUnsupportedSymlinkError(error)) return false; + throw error; + } finally { + fs.rmSync(probeDir, { recursive: true, force: true }); + } +} + +const symlinkSupported = canCreateSymlink(); + // --------------------------------------------------------------------------- // classifyFile tests // --------------------------------------------------------------------------- @@ -211,17 +231,12 @@ describe('SquadObserver', () => { expect(spans.some(s => s.name === 'squad.observer.file_change')).toBe(false); }); - it('skips symlinked directories when resolving root directory events', async () => { + (symlinkSupported ? it : it.skip)('skips symlinked directories when resolving root directory events', async () => { const outsideDir = fs.mkdtempSync(path.join(os.tmpdir(), 'squad-observer-outside-')); try { fs.writeFileSync(path.join(outsideDir, 'newest.md'), 'outside'); const symlinkPath = path.join(squadDir, 'linked-outside'); - try { - fs.symlinkSync(outsideDir, symlinkPath, 'dir'); - } catch (error) { - if (isUnsupportedSymlinkError(error)) return; - throw error; - } + fs.symlinkSync(outsideDir, symlinkPath, 'dir'); const olderFile = path.join(squadDir, 'team.md'); fs.writeFileSync(olderFile, 'inside');