From 9ceadb3482b33ed23bb35a9edcad6b9e1428bbca Mon Sep 17 00:00:00 2001 From: alexion Date: Thu, 16 Jul 2026 22:13:38 -0400 Subject: [PATCH] fix: make bench arm bin provisioning idempotent across trials provisionArmBin symlinks the shell arm's one allow-listed binary into a per-sitting bin directory, but a sitting's trials share that directory and re-provision it each trial, so the second trial's symlinkSync threw EEXIST. This crashed every shell arm (gitea-axi, tea, raw-api) at more than one trial per sitting; the gitea-mcp arm was unaffected because its shell is disabled and it symlinks nothing. Remove any existing link before re-creating it so provisioning is idempotent. Add a regression test that provisions the same bin directory twice and asserts it neither throws nor duplicates the link. --- bench/guard.test.ts | 14 ++++++++++++++ bench/guard.ts | 9 +++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/bench/guard.test.ts b/bench/guard.test.ts index 80d60d9..109674b 100644 --- a/bench/guard.test.ts +++ b/bench/guard.test.ts @@ -219,4 +219,18 @@ describe("provisionArmBin", () => { it("throws when the arm's binary cannot be located", () => { expect(() => provisionArmBin("tea", binDir, () => null)).toThrow(/tea/); }); + + it("is idempotent across the trials of one sitting sharing a bin directory", () => { + // A benchmark sitting runs several trials against one per-sitting bin + // directory, so provisionArmBin is called once per trial on the same dir. + // A repeat call must not throw and must leave a single symlink, not a + // duplicate or a partially-clobbered link. + expect(() => { + provisionArmBin("gitea-axi", binDir, locate); + provisionArmBin("gitea-axi", binDir, locate); + }).not.toThrow(); + + expect(readdirSync(binDir)).toEqual(["gitea-axi"]); + expect(readlinkSync(join(binDir, "gitea-axi"))).toBe("/fake/prefix/gitea-axi"); + }); }); diff --git a/bench/guard.ts b/bench/guard.ts index 3e8c806..849ffb8 100644 --- a/bench/guard.ts +++ b/bench/guard.ts @@ -1,4 +1,4 @@ -import { accessSync, constants, mkdirSync, symlinkSync } from "node:fs"; +import { accessSync, constants, mkdirSync, rmSync, symlinkSync } from "node:fs"; import { join } from "node:path"; import type { Arm } from "./result.js"; @@ -303,7 +303,12 @@ export function provisionArmBin( `cannot provision the ${arm} arm: its binary "${binary}" was not found on PATH`, ); } - symlinkSync(target, join(binDir, binary)); + // Idempotent: the trials in one sitting share a single bin directory, so a prior + // trial may have already created this link. Remove any existing entry before + // re-linking rather than letting symlinkSync fail with EEXIST on the second trial. + const linkPath = join(binDir, binary); + rmSync(linkPath, { force: true }); + symlinkSync(target, linkPath); } /** Resolve `binary` to the absolute path of the first executable of that name on PATH. */