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
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### Concurrent first use no longer fails with "Text file busy" on Linux

When several tool calls hit an empty shared-runtime slot at the same time, one guard
hydrates the slot and the others wait. On Linux the kernel refuses to run a file while
another process still holds it open for writing. It reports this as "Text file busy". A
waiting guard could reach the run step in that brief window, fail to start the helper, and
deny the tool call by mistake. This showed up as a flaky Linux CI failure under contention.
macOS and Windows do not enforce this rule, so only Linux saw the denial.

The guard now retries the helper a bounded number of times when the start fails with this
exact condition, then hands off as before. The retry is short and self-clearing: the peer
closes the file the moment its write finishes, so the next attempt starts the helper. A
helper that genuinely cannot run still returns the same status after the retries, so no real
failure is hidden. The runtime binary is still written atomically, so the fix only closes
the read-side race.
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,24 @@ if [[ -z "$EXPECTED" || "$ACTUAL" != "$EXPECTED" ]]; then
exit 2
fi

exec "$HELPER" bootstrap-safety-hook --host "$HOST" --repo "$ROOT"
# Linux refuses to exec a file another process still holds open for writing
# (ETXTBSY, surfaced as exit 126). Under concurrent first use a peer guard can be
# finishing hydration at this instant, even though the writer replaces the binary
# atomically. Retry briefly, then hand off. A genuinely non-executable helper keeps
# returning 126 and the final status still propagates unchanged. Running the helper
# as a child (not exec) is required so a failed start is observable; stdio and the
# exit code pass through, and an ETXTBSY start never consumes stdin.
ATTEMPT=0
while :; do
"$HELPER" bootstrap-safety-hook --host "$HOST" --repo "$ROOT"
HELPER_STATUS=$?
if [[ $HELPER_STATUS -eq 126 && $ATTEMPT -lt 30 ]]; then
ATTEMPT=$((ATTEMPT + 1))
sleep 0.1
continue
fi
exit $HELPER_STATUS
done
`, Version, SourceCommit, Version, SourceCommit, Version, Version, runtimeHydrateCommandBash(Version), runtimeHydrateCommandBash(Version)))
}

Expand Down
Loading