Skip to content

fix(boatstack): guard retries helper on Linux ETXTBSY under concurrent first use - #195

Merged
bigboateng merged 1 commit into
mainfrom
fix/guard-etxtbsy-retry
Jul 26, 2026
Merged

fix(boatstack): guard retries helper on Linux ETXTBSY under concurrent first use#195
bigboateng merged 1 commit into
mainfrom
fix/guard-etxtbsy-retry

Conversation

@bigboateng

Copy link
Copy Markdown
Contributor

Why

The two open Sync Boatstack PRs in operatorstack/boatstack (#113, #109) fail only on test (ubuntu-latest):

--- FAIL: TestGuardAutoHydrationSerializesConcurrentFirstUse
  guard 5 did not proceed under contention: err=exit status 126
  .../runtimes/dev/unknown/linux-amd64/boatstack-helper: Text file busy

Text file busy (ETXTBSY, exit 126) is a Linux-only kernel rule: you cannot exec a 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 → Synccloseos.Rename), so no write fd survives the rename. The race is purely read-side: the guard's final exec "$HELPER" (hooks.go) has no retry, and ETXTBSY is inherently racy on Linux even against atomic writes.

Fix

Replace the guard's terminal exec with a bounded retry that re-runs only on exit 126 (≤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 (not exec) 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.

…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
@bigboateng
bigboateng merged commit d4c1a8e into main Jul 26, 2026
11 checks passed
@bigboateng
bigboateng deleted the fix/guard-etxtbsy-retry branch July 26, 2026 02:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant