fix(boatstack): guard retries helper on Linux ETXTBSY under concurrent first use - #195
Merged
Conversation
…rrent first use Linux refuses to exec a file another process still holds open for writing (ETXTBSY, exit 126). Under concurrent first use a waiting guard could reach the exec step while a peer was still finishing hydration and deny the tool call by mistake — a flaky Linux-only CI failure (TestGuardAutoHydrationSerializesConcurrentFirstUse); macOS and Windows do not enforce the rule. The writer already replaces the binary atomically (temp → close → rename), so the race is purely read-side. Replace the guard's final 'exec $HELPER' with a bounded retry that re-runs only on exit 126 and then propagates the helper's real status. Running the helper as a child (not exec) makes a failed start observable; stdio and the exit code pass through and an ETXTBSY start never consumes stdin. Windows/macOS guards are unchanged (no ETXTBSY there). Disclosure-Reviewed: reviewed — public-safe only, private facet kept out of this commit
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The two open Sync Boatstack PRs in
operatorstack/boatstack(#113, #109) fail only ontest (ubuntu-latest):Text file busy(ETXTBSY, exit 126) is a Linux-only kernel rule: you cannotexeca file while any process holds it open for writing. Under concurrent first-use, a waiting guard reaches the exec step while a peer is finishing hydration → the guard dies with no retry and denies the tool call. macOS/Windows do not enforce this, which is why only ubuntu fails.Root cause
The writer is already correct — the binary is replaced atomically (
atomicWriteMode: temp →Chmod→ write →Sync→ close →os.Rename), so no write fd survives the rename. The race is purely read-side: the guard's finalexec "$HELPER"(hooks.go) has no retry, and ETXTBSY is inherently racy on Linux even against atomic writes.Fix
Replace the guard's terminal
execwith a bounded retry that re-runs only on exit126(≤30 attempts, 0.1s apart = self-clearing in ms once the peer closes the file), then propagates the helper's real status. Running the helper as a child (notexec) makes a failed start observable; stdio + exit code pass through, and an ETXTBSY start never consumes stdin. Windows/macOS guards unchanged.This is the standard ETXTBSY remedy (Go toolchain, bazel, etc.). The existing test correctly caught a real bug, so it is unchanged — it should now pass on Linux.
Result
Once merged, the projection re-syncs and the sync PRs go green; the failure mode is removed at the source, so future syncs won't hit it again.
Tests
Full package suite green on macOS; gofmt/vet clean; release-note preflight passes. Linux behavior is validated by the sync PR's ubuntu job after merge.