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
5 changes: 5 additions & 0 deletions .changeset/short-rstest-socket-roots.md
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 7 additions & 3 deletions docs/local-ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,13 @@ 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, worker ID, and
invocation identity to `/tmp/ab-rstest-<hash16>` 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`.

The three Verify legs below mirror the hosted `main`-push matrix. On PRs,
only `verify (24)` runs hosted.
Expand Down
29 changes: 29 additions & 0 deletions packages/agent-bundle/tests/rstest-worker-isolation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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);
});

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);
});
21 changes: 20 additions & 1 deletion rstest.worker-isolation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createHash } from 'node:crypto';
import { mkdirSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
Expand All @@ -6,8 +7,26 @@ export const rstestWorkerId = (): string => process.env['RSTEST_WORKER_ID'] ?? '

const hostTemporaryRoot = tmpdir();

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}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep hashed worker roots in the cleanup lifecycle

When check:local-ci is run repeatedly, scripts/local-ci.mjs deletes and recreates only the per-leg TMPDIR, but this path now lives beside it directly under /tmp. Consequently, worker caches such as cache/cmd-<pid>-<serial> and any fixtures left by interrupted tests survive every rerun and accumulate indefinitely, defeating the runner's documented clean-temp guarantee and potentially exhausting /tmp or contaminating later runs. Ensure these hashed roots are removed as part of each run or otherwise tie their lifetime to the per-leg directory.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #397 (merged as d25a9c6). rstest.worker-isolation.ts writes an owner marker (.ab-rstest-owner.json with pid + start time) into each hashed worker root, and scripts/local-ci.mjs calls the new scripts/rstest-worker-roots.mjs to remove only roots whose owner has exited — other lanes' live roots are never touched. Proven by tests/rstest-worker-isolation.test.ts; documented in docs/local-ci.md.

};

export const rstestWorkerRoot = (): string => {
const root = join(hostTemporaryRoot, 'agent-bundle-rstest-w' + rstestWorkerId());
const root = rstestWorkerRootPath(hostTemporaryRoot, rstestWorkerId());
mkdirSync(root, { recursive: true });
return root;
};
Expand Down
Loading