From 6f10d7cad1986e0ebd8b4f6e6db41a08fc1fd1e3 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 2 Sep 2026 23:34:14 +0000 Subject: [PATCH 1/2] fix(test): hash Rstest worker temp roots (#335) --- .changeset/short-rstest-socket-roots.md | 5 +++++ docs/local-ci.md | 9 +++++--- .../tests/rstest-worker-isolation.test.ts | 22 +++++++++++++++++++ rstest.worker-isolation.ts | 18 ++++++++++++++- 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 .changeset/short-rstest-socket-roots.md create mode 100644 packages/agent-bundle/tests/rstest-worker-isolation.test.ts diff --git a/.changeset/short-rstest-socket-roots.md b/.changeset/short-rstest-socket-roots.md new file mode 100644 index 000000000..b508c476b --- /dev/null +++ b/.changeset/short-rstest-socket-roots.md @@ -0,0 +1,5 @@ +--- +"agent-bundle": patch +--- + +Keep Doctor socket fixtures reliable under long local-CI temporary directory names by deriving short, isolated Rstest worker roots. diff --git a/docs/local-ci.md b/docs/local-ci.md index b0975cf18..c1748af71 100644 --- a/docs/local-ci.md +++ b/docs/local-ci.md @@ -42,9 +42,12 @@ each other's temp traffic: suites that assert temp-root hygiene (for example `cli.test.ts` scans `os.tmpdir()` for leaked `agent-bundle-artifact-*` directories) only ever see their own leg's directories, so a sibling leg's in-flight work cannot fail them — while a directory the leg itself leaks -still fails its own scan. Legs live under `.worktrees/local-ci/` -(gitignored), are reused across runs for warm caches, and can be recreated -with `--fresh`. +still fails its own scan. Rstest re-hashes that leg directory and worker ID +to `/tmp/ab-rstest-` before exposing its worker `TMPDIR`; this leaves +headroom below Linux's 108-byte `sun_path` cap for nested socket fixtures +without sacrificing per-leg or per-worker isolation. Legs live under +`.worktrees/local-ci/` (gitignored), are reused across runs for warm caches, +and can be recreated with `--fresh`. The three Verify legs below mirror the hosted `main`-push matrix. On PRs, only `verify (24)` runs hosted. diff --git a/packages/agent-bundle/tests/rstest-worker-isolation.test.ts b/packages/agent-bundle/tests/rstest-worker-isolation.test.ts new file mode 100644 index 000000000..992437ce5 --- /dev/null +++ b/packages/agent-bundle/tests/rstest-worker-isolation.test.ts @@ -0,0 +1,22 @@ +import { join } from 'node:path'; + +import { expect, it } from '@rstest/core'; + +import { rstestWorkerRootPath } from '../../../rstest.worker-isolation.ts'; + +it('keeps Doctor socket fixtures below the Linux AF_UNIX pathname cap', () => { + const longLocalCiRoot = join( + '/tmp', + `abci-repository-hash-${'verify-current-pathological-node-version-'.repeat(3)}`, + ); + const workerRoot = rstestWorkerRootPath(longLocalCiRoot, '123', 'linux'); + const longestDoctorEndpoint = join( + workerRoot, + 'agent-bundle-doctor-XXXXXX', + 'endpoints', + 'event-identity.sock', + ); + + // Linux sun_path is 108 bytes including NUL; 96 leaves 12 bytes of headroom. + expect(Buffer.byteLength(longestDoctorEndpoint, 'utf8') + 1).toBeLessThanOrEqual(96); +}); diff --git a/rstest.worker-isolation.ts b/rstest.worker-isolation.ts index 7ada03076..690699acb 100644 --- a/rstest.worker-isolation.ts +++ b/rstest.worker-isolation.ts @@ -1,3 +1,4 @@ +import { createHash } from 'node:crypto'; import { mkdirSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; @@ -6,8 +7,23 @@ export const rstestWorkerId = (): string => process.env['RSTEST_WORKER_ID'] ?? ' const hostTemporaryRoot = tmpdir(); +export const rstestWorkerRootPath = ( + temporaryRoot: string, + workerId: string, + platform: NodeJS.Platform = process.platform, +): string => { + if (platform === 'win32') return join(temporaryRoot, 'agent-bundle-rstest-w' + workerId); + const hash = createHash('sha256') + .update(temporaryRoot, 'utf8') + .update('\0', 'utf8') + .update(workerId, 'utf8') + .digest('hex') + .slice(0, 16); + return join('/tmp', `ab-rstest-${hash}`); +}; + export const rstestWorkerRoot = (): string => { - const root = join(hostTemporaryRoot, 'agent-bundle-rstest-w' + rstestWorkerId()); + const root = rstestWorkerRootPath(hostTemporaryRoot, rstestWorkerId()); mkdirSync(root, { recursive: true }); return root; }; From 919dcb7bac065fb792d0d0faf58ce8740bc0fcec Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 3 Sep 2026 00:16:57 +0000 Subject: [PATCH 2/2] fix(test): isolate concurrent Rstest invocations Include the worktree/process invocation identity in short worker roots so parallel runs cannot share temporary fixtures. --- docs/local-ci.md | 9 +++++---- .../agent-bundle/tests/rstest-worker-isolation.test.ts | 7 +++++++ rstest.worker-isolation.ts | 3 +++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/local-ci.md b/docs/local-ci.md index c1748af71..56e270710 100644 --- a/docs/local-ci.md +++ b/docs/local-ci.md @@ -42,10 +42,11 @@ each other's temp traffic: suites that assert temp-root hygiene (for example `cli.test.ts` scans `os.tmpdir()` for leaked `agent-bundle-artifact-*` directories) only ever see their own leg's directories, so a sibling leg's in-flight work cannot fail them — while a directory the leg itself leaks -still fails its own scan. Rstest re-hashes that leg directory and worker ID -to `/tmp/ab-rstest-` before exposing its worker `TMPDIR`; this leaves -headroom below Linux's 108-byte `sun_path` cap for nested socket fixtures -without sacrificing per-leg or per-worker isolation. Legs live under +still fails its own scan. Rstest re-hashes that leg directory, worker ID, and +invocation identity to `/tmp/ab-rstest-` before exposing its worker +`TMPDIR`; this leaves headroom below Linux's 108-byte `sun_path` cap for nested +socket fixtures without sacrificing per-leg, per-worker, or concurrent-run +isolation. Legs live under `.worktrees/local-ci/` (gitignored), are reused across runs for warm caches, and can be recreated with `--fresh`. diff --git a/packages/agent-bundle/tests/rstest-worker-isolation.test.ts b/packages/agent-bundle/tests/rstest-worker-isolation.test.ts index 992437ce5..c4cc985a4 100644 --- a/packages/agent-bundle/tests/rstest-worker-isolation.test.ts +++ b/packages/agent-bundle/tests/rstest-worker-isolation.test.ts @@ -20,3 +20,10 @@ it('keeps Doctor socket fixtures below the Linux AF_UNIX pathname cap', () => { // Linux sun_path is 108 bytes including NUL; 96 leaves 12 bytes of headroom. expect(Buffer.byteLength(longestDoctorEndpoint, 'utf8') + 1).toBeLessThanOrEqual(96); }); + +it('isolates concurrent Rstest invocations that share a host temporary root', () => { + const firstRoot = rstestWorkerRootPath('/tmp', '1', 'linux', '/workspace/first\0' + '101'); + const secondRoot = rstestWorkerRootPath('/tmp', '1', 'linux', '/workspace/second\0' + '202'); + + expect(firstRoot).not.toBe(secondRoot); +}); diff --git a/rstest.worker-isolation.ts b/rstest.worker-isolation.ts index 690699acb..4329eb0e9 100644 --- a/rstest.worker-isolation.ts +++ b/rstest.worker-isolation.ts @@ -11,12 +11,15 @@ export const rstestWorkerRootPath = ( temporaryRoot: string, workerId: string, platform: NodeJS.Platform = process.platform, + invocationId: string = process.cwd() + '\0' + String(process.pid), ): string => { if (platform === 'win32') return join(temporaryRoot, 'agent-bundle-rstest-w' + workerId); const hash = createHash('sha256') .update(temporaryRoot, 'utf8') .update('\0', 'utf8') .update(workerId, 'utf8') + .update('\0', 'utf8') + .update(invocationId, 'utf8') .digest('hex') .slice(0, 16); return join('/tmp', `ab-rstest-${hash}`);