Skip to content

test(did-git-sign): serialise the hook tests that exec what they just wrote - #54

Merged
stormer78 merged 1 commit into
mainfrom
fix/hook-exec-race
Sep 13, 2026
Merged

stormer78 merged 1 commit into
mainfrom
fix/hook-exec-race

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

Blocks the 0.4.10 release (#53), whose Test job failed twice on this.

The failure

init::tests::commit_msg_hook_* panics with Os { code: 26, kind: ExecutableFileBusy, message: "Text file busy" }, and the test that fails moves between runscommit_msg_hook_adds_signoff_only_when_opted_in one run, commit_msg_hook_is_idempotent the next. That is what made it read as one flaky test rather than a shared race. Seen on main in run 34704223744 and twice on #53, where a re-run did not clear it.

The cause

Not the write. write_executable_hook uses std::fs::write, which closes its handle before returning.

It is a race across test threads. Four tests install a hook and then execute it — three via Command::new(&hook), one via git commit. When one test forks a child (any Command::spawn) while another still holds its hook open for writing, the child inherits that descriptor for the moment between fork and exec, and Linux refuses to exec a file that any process holds open for writing. Hence the intermittency, and hence which test loses being effectively random.

The fix

#[serial_test::serial] on those four, with a comment explaining why so it is not stripped later as redundant. serial_test is already a dev-dependency of this crate and this module already uses it at install_hook_dispatcher_refuses_to_take_a_local_hooks_path_it_does_not_own, for the same class of reason (a process-global concern).

Production is unaffected — there, git execs the hook long after installation, so the window does not exist.

Verification, and its limit

cargo fmt --all -- --check clean, cargo clippy --workspace --all-targets -- -D warnings silent, five consecutive runs of CI's exact cargo test -p did-git-sign --features insecure-policy-bypass at 73/73, and cargo test --workspace at 162/162.

Worth being straight about the limit: those runs are macOS, where ETXTBSY does not arise — the behaviour is Linux-specific and CI is Linux, so this PR's own CI is the real evidence, not my local runs. Serialising is the right remedy regardless, on the reasoning above.

After this

Rebase #53 onto main, get a green run on the release bump, then the v0.4.10 tag.

… wrote

Four tests install a commit-msg hook and then execute it, three directly and one through git commit. Run in parallel they fail intermittently on Linux with ETXTBSY (Text file busy), and the test that fails moves between runs, which is what made this look like one flaky test rather than a shared race. The write is not the cause: write_executable_hook closes its handle before returning. The race is across threads. When one test forks a child - any Command::spawn - while another still holds its hook open for writing, the child inherits that descriptor for the moment between fork and exec, and Linux refuses to exec a file any process holds open for writing. Serialising the four removes the overlap, using the serial_test dev-dependency this module already applies to a CWD-global test. Production is unaffected: there, git execs the hook long after installation. Seen today on main (run 34704223744) and twice on the 0.4.10 release branch, where a re-run did not clear it.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 requested review from a team as code owners September 13, 2026 05:12
@stormer78
stormer78 merged commit f9762ba into main Sep 13, 2026
7 checks passed
@stormer78
stormer78 deleted the fix/hook-exec-race branch September 13, 2026 05:15
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