Skip to content

Fix ETXTBSY race in claude-swap CLI card test - #2644

Merged
steipete merged 2 commits into
steipete:mainfrom
urda-forks:urda/flakey-test
Aug 5, 2026
Merged

Fix ETXTBSY race in claude-swap CLI card test#2644
steipete merged 2 commits into
steipete:mainfrom
urda-forks:urda/flakey-test

Conversation

@urda

@urda urda commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a flaky linux-arm64 failure in CLICardsClaudeSwapTests. The test wrote a
shell script, set mode 0755, and executed it immediately. Under
swift test --parallel that races with every other test that spawns a child
process, and execve fails with ETXTBSY.

Tests now write the script body as data and execute a checked-in trampoline that
reads it, so execve only ever touches a file no test process has written.

Root cause

Swift Testing runs the suite concurrently inside one process, so all tests share
one file descriptor table. The sequence:

  1. Data.write(to:) opens a write descriptor on the new script.
  2. Another test calls Process.run() and forks. The child inherits that
    descriptor, because Linux Foundation has no POSIX_SPAWN_CLOEXEC_DEFAULT.
  3. The test executes the script. execve returns ETXTBSY, because the inode is
    still open for writing in the child.

Foundation flattens the errno and reports NSCocoaErrorDomain error 256, which is
why the original CI failure named no cause.

macOS never hits this. Darwin's Foundation spawns with
POSIX_SPAWN_CLOEXEC_DEFAULT, so children do not inherit the descriptor.

Scope

CodexBarLinuxTests (TestsLinux) is the target the Linux job compiles;
CodexBarTests is macOS-only (Package.swift:190-204). Three sites in
TestsLinux used the racing pattern, and all three ran in the failing job:

  • CLICardsClaudeSwapTests.swift, the test that actually flaked
  • PlatformGatingTests.swift, which executes a fake claude CLI
  • AntigravityCLIStrategyLinuxTests.swift

The mirrored Tests/CodexBarTests/CLICardsClaudeSwapTests.swift is updated too, so
the two trees stay in step. The remaining write-then-execute sites under
Tests/CodexBarTests are macOS-only and cannot hit this race.

The suites supplying the racing forks are ShellCommandSessionLinuxTests,
ProcessPipeCaptureLinuxTests, CostUsageScanExecutorLinuxTests, and
HookDispatchTests.

Verification

Reproduced and measured in swift:6.3.3-noble on arm64, matching the CI runner. A
standalone harness ran the same shape: Data.write(to:) for the writer,
Foundation.Process for the forkers, and posix_spawn for the launch so the
errno stays visible.

shape attempts ETXTBSY
write, chmod, exec (current) 600 112
trampoline (this PR) 600 0

Also run:

  • swift test --parallel on Linux arm64 in Docker: 355 tests in 53 suites passed.
  • swift test --filter CLICardsClaudeSwapTests on macOS: 26 tests passed.
  • make check: 0 violations in 1756 files.

Notes

The script heredocs are unchanged, so each test still shows what its fake CLI
does. $0 inside a shebang script is the path handed to execve, that is the
symlink, on both Linux and macOS. That is verified on both platforms and is what
lets "$0.sh" resolve per test.

Found while running CI for #2640, a docs-only change that had no way to cause it.
Failing job:
https://github.com/steipete/CodexBar/actions/runs/30885238832/job/91914882363

The test wrote a shell script, set mode 0755, and executed it immediately.
Under swift test --parallel a concurrent fork inherits the still-open write
descriptor, so execve fails with ETXTBSY and the launch reports Cocoa 256.

Write the script body as data and execute a checked-in trampoline that reads
it, so execve only ever touches a file no test process has written.

Measured in swift:6.3.3-noble on arm64: 112 failures in 600 attempts with the
old shape, 0 in 600 with the trampoline.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 482feb758b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Tests/CodexBarTests/CLICardsClaudeSwapTests.swift Outdated
@urda

urda commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Note for reviewers: this flake surfaced on #2640, a two-file docs change that had
no path to causing it. That PR is currently showing a red check for this exact
failure, and I cannot rerun the job without admin rights on this repository.

Once this merges, I will rebase #2640 onto main so its checks rerun with the
fix in place. The two PRs are otherwise independent, so this one does not need to
wait on that one.

The first commit only touched Tests/CodexBarTests, which Package.swift builds
on macOS alone. The Linux job compiles CodexBarLinuxTests from TestsLinux, so
the race remained in the target that actually flaked.

Convert all three TestsLinux sites through a shared FakeExecutable helper, and
keep the macOS mirror in step.

Verified on Linux arm64 in swift:6.3.3-noble: 355 tests in 53 suites pass.
@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal priority bug or improvement with limited blast radius. labels Aug 4, 2026
@clawsweeper

clawsweeper Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codex review: needs changes before merge. Reviewed August 4, 2026, 3:59 PM ET / 19:59 UTC.

ClawSweeper review

What this changes

The PR replaces directly executed temporary fake CLI scripts in Linux tests with a stable checked-in shell trampoline to avoid parallel-test ETXTBSY failures.

Merge readiness

⚠️ Ready for maintainer review - 3 items remain

Keep this PR open for a narrow cleanup: the ETXTBSY repair is credible and proven, but it leaves temporary UUID-named script sidecars behind in two Linux test paths.

Priority: P2
Reviewed head: 470024ced8dbb366b5abc1144234bf69624ce1c9

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) The ETXTBSY diagnosis and Linux proof are strong; a small, concrete temporary-file cleanup remains.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (live_output): The PR body provides after-fix Linux arm64 harness results and parallel-suite output, corroborated by green Linux CI; no contributor proof action is needed.
Patch quality 🐚 platinum hermit (4/6) 1 actionable review finding remain.

Verification

Check Result Evidence
Real behavior Verified Sufficient (live_output): The PR body provides after-fix Linux arm64 harness results and parallel-suite output, corroborated by green Linux CI; no contributor proof action is needed.
Evidence reviewed 5 items Current main still has the vulnerable shape: The Linux Claude-swap test still writes and immediately executes its temporary script on current main; the PR is therefore still necessary.
Sidecar cleanup remains incomplete: The proposed helper writes <url>.sh, while the Platform Gating callers remove only <url>; the Antigravity caller has the same cleanup shape, leaving UUID-named files in the temporary directory.
Prior finding is still present: The previous completed review identified this same sidecar cleanup defect at the current head, and no newer head commit is present in the supplied PR metadata.
Findings 1 actionable finding [P3] Remove the generated script sidecars
Security None None.

How this fits together

CodexBar’s Linux provider tests create fake command-line tools and launch them through provider probes. The new helper writes each test-specific body beside a stable trampoline, which passes the resulting command output back to the probe under test.

flowchart LR
A[Linux provider tests] --> B[Fake CLI body]
B --> C[Temporary sidecar script]
A --> D[Stable trampoline]
D --> E[Shell runs sidecar]
E --> F[Provider probe result]
Loading

Before merge

  • Remove the generated script sidecars (P3) - install creates &lt;url>.sh, but the affected Platform Gating and Antigravity callers defer removal of only &lt;url>. Each run therefore leaves a UUID-named script in the temporary directory; make cleanup remove both artifacts or centralize it in the helper.
  • Resolve merge risk (P1) - Merging the helper as written leaves one temporary .sh sidecar for each affected Platform Gating and Antigravity test run, causing avoidable accumulation in the test temporary directory.
  • Complete next step (P2) - A mechanical cleanup repair can preserve the PR’s approach while eliminating the temporary-file leak.

Findings

  • [P3] Remove the generated script sidecars — TestsLinux/FakeExecutableSupport.swift:16
Agent review details

Security

None.

Review metrics

Metric Value Why it matters
Test-only code growth production +0, tests +81/-10 across 7 files The added shared helper and trampoline cover three Linux fake-executable sites without changing shipping application code.

Merge-risk options

Maintainer options:

  1. Decide the mitigation before merge
    Keep the trampoline approach, but give the helper or both callers ownership of deleting both the symlink and its adjacent script body.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Technical review

Best possible solution:

Keep the trampoline approach, but give the helper or both callers ownership of deleting both the symlink and its adjacent script body.

Do we have a high-confidence way to reproduce the issue?

Yes. The contributor reports a Linux arm64 harness that reproduced ETXTBSY 112 times in 600 attempts with the existing write/chmod/exec shape and zero times with the trampoline.

Is this the best way to solve the issue?

Yes, with one small correction. Executing a stable trampoline avoids executing the just-written inode, but cleanup must also remove the generated sidecar.

Full review comments:

  • [P3] Remove the generated script sidecars — TestsLinux/FakeExecutableSupport.swift:16
    install creates <url>.sh, but the affected Platform Gating and Antigravity callers defer removal of only <url>. Each run therefore leaves a UUID-named script in the temporary directory; make cleanup remove both artifacts or centralize it in the helper.
    Confidence: 0.99

Overall correctness: patch is correct
Overall confidence: 0.98

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against a82f509ea8e7.

Labels

Label justifications:

  • P2: This is a reproducible Linux CI reliability defect affecting parallel provider-test coverage, with a focused repair and credible evidence.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body provides after-fix Linux arm64 harness results and parallel-suite output, corroborated by green Linux CI; no contributor proof action is needed.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body provides after-fix Linux arm64 harness results and parallel-suite output, corroborated by green Linux CI; no contributor proof action is needed.

Evidence

Acceptance criteria:

  • [P1] swift test --filter CLICardsClaudeSwapTests.
  • [P1] swift test --filter PlatformGatingTests.
  • [P1] swift test --filter AntigravityCLIStrategyLinuxTests.
  • [P1] make check.
  • [P1] make test.

What I checked:

  • Current main still has the vulnerable shape: The Linux Claude-swap test still writes and immediately executes its temporary script on current main; the PR is therefore still necessary. (TestsLinux/CLICardsClaudeSwapTests.swift:375, a82f509ea8e7)
  • Sidecar cleanup remains incomplete: The proposed helper writes .sh, while the Platform Gating callers remove only ; the Antigravity caller has the same cleanup shape, leaving UUID-named files in the temporary directory. (TestsLinux/FakeExecutableSupport.swift:16, 470024ced8db)
  • Prior finding is still present: The previous completed review identified this same sidecar cleanup defect at the current head, and no newer head commit is present in the supplied PR metadata. (TestsLinux/PlatformGatingTests.swift:32, 470024ced8db)
  • Real Linux behavior evidence: The PR body reports a Linux arm64 harness reducing ETXTBSY from 112 failures in 600 attempts to zero, plus a passing parallel Linux suite; all supplied Linux and macOS checks are green. (470024ced8db)
  • Area history: Platform Gating fixture isolation appears in Peter Steinberger’s commit, while the Linux Claude-swap invocation proof appears in Kiran Magic’s commit. (TestsLinux/PlatformGatingTests.swift:218, a3cac151f9c0)

Likely related people:

  • Peter Steinberger: Authored the Platform Gating fixture-isolation change and the macOS Claude-swap proof hardening. (role: recent area contributor; confidence: high; commits: a3cac151f9c0, 7b63ab4f21cc; files: TestsLinux/PlatformGatingTests.swift, Tests/CodexBarTests/CLICardsClaudeSwapTests.swift)
  • Kiran Magic: Authored the Linux Claude-swap invocation-proof hardening that owns one affected fake executable path. (role: introduced Linux test coverage; confidence: high; commits: f832115ba2ec; files: TestsLinux/CLICardsClaudeSwapTests.swift)

Rank-up moves

Optional improvements that raise the rating; they are not merge blockers.

  • Remove both the trampoline symlink and its <url>.sh sidecar in the affected cleanup paths.
  • Re-run the focused Linux tests and the normal check suite after the cleanup.

Rating scale

Score Internal tier Crab rank Meaning
6/6 S 🦀 challenger crab Exceptional readiness
5/6 A 🦞 diamond lobster Very strong readiness
4/6 B 🐚 platinum hermit Good normal PR; ordinary maintainer review
3/6 C 🦐 gold shrimp Useful, but confidence is limited
2/6 D 🦪 silver shellfish Proof or implementation needs work
1/6 F 🧂 unranked krab Not merge-ready
N/A NA 🌊 off-meta tidepool Rating does not apply

Overall follows the weaker of proof and patch quality.
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

Workflow

  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

History

Review history (3 earlier review cycles)
  • reviewed 2026-08-04T07:52:09.618Z sha 470024c :: needs changes before merge. :: [P3] Clean up the generated script sidecar
  • reviewed 2026-08-04T08:18:10.184Z sha 470024c :: needs changes before merge. :: [P3] Clean up the generated script sidecar
  • reviewed 2026-08-04T15:02:51.761Z sha 470024c :: needs changes before merge. :: [P3] Remove the generated script sidecar

@steipete
steipete merged commit 823fa91 into steipete:main Aug 5, 2026
9 checks passed
@steipete

steipete commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Landed. Verification before merge (independent maintainer-agent review):

  • Confirmed the trampoline mechanism structurally removes the ETXTBSY window: tests never execve a file they wrote; only the checked-in exec-trampoline.sh inode (committed 0755, never opened for writing) is executed, and exec /bin/sh "$0.sh" "$@" preserves argv, so the invocation-marker assertions still exercise the original behavior.
  • Audited the whole TestsLinux target for remaining write-then-exec patterns: none left (other posixPermissions sites are permission-assertion tests on non-executed files). Remaining macOS-only patterns are covered by POSIX_SPAWN_CLOEXEC_DEFAULT.
  • Local macOS: swift test --filter CLICardsClaudeSwapTests — 26/26 pass. Linux repro: Docker swift:6.3.3 (arm64), swift test --filter 'CLICardsClaudeSwapTests|PlatformGatingTests|AntigravityCLIStrategyLinuxTests' — 26 tests, 3 suites, all pass.

@urda
urda deleted the urda/flakey-test branch August 5, 2026 02:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants