Skip to content

Write unit-test executables from a child process - #483

Merged
JacobStephens2 merged 1 commit into
mainfrom
fix-text-file-busy
Oct 7, 2026
Merged

JacobStephens2 merged 1 commit into
mainfrom
fix-text-file-busy

Conversation

@JacobStephens2

Copy link
Copy Markdown
Owner

Summary

Linux CI fails now and then with Text file busy (os error 26) in unit tests that write a fake CLI and then run it. Tonight that failed CI on #478 (a_transport_failure_stays_pending_while_the_child_saves_work) and #433 (distinct_children_deliver_out_of_order_with_their_own_endings_and_accounts).

Cause: fs::write holds the file open for writing in the test process. If another test thread forks a process while it's open, that process inherits the descriptor until it execs, and running the file fails meanwhile. The scenario tests already avoid this by copying thirdshift with cp (tests/support/mod.rs).

test_support::write_executable now has a child sh write the file and chmod 755 it, so the test process never holds it open. All six unit-test sites that wrote an executable use it: the git, github, session, child_run and harness interpretation fixtures and the git hook test.

Evidence

  • cargo clippy --all-targets -- -D warnings and cargo fmt --check clean.
  • cargo test --bin thirdshift: 676 passed, then the unit binary 5 more times: 4 clean runs, and 1 that hit the unrelated interrupt-timing flake in harness::settings tests (not touched here).
  • The race isn't reproducible on demand, so there's no red/green test. The fix removes the open write descriptor from the test process, which is the only way the busy error can arise.

Not addressed

  • harness::settings interrupt tests (an_interrupted_model_check_is_an_error_instead_of_retrying_the_pair, an_interrupted_catalog_read_is_an_error_instead_of_retention) are flaky: SIGINT's handler can run on another thread after the code under test has already checked for the interrupt.
  • session::execution_tests::startup_failure_preserves_its_cause_without_completion_reporting runs the real opencode when one is installed: with the fake made non-executable, the PATH search skips it and finds the real CLI, so locally the test hangs (and starts a real session). CI has no OpenCode installed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ts55nZd9iVAg8GP3QxHo9Y

Unit tests that wrote a fake CLI with fs::write and then ran it failed now
and then with "Text file busy": the write's file descriptor was inherited
by a process another test thread spawned meanwhile, so the file couldn't be
executed until that process exec'd. test_support::write_executable has a
child process write the file instead, as the scenario tests already copy
thirdshift with cp for the same reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ts55nZd9iVAg8GP3QxHo9Y
@JacobStephens2
JacobStephens2 merged commit de64bd6 into main Oct 7, 2026
13 checks passed
@JacobStephens2
JacobStephens2 deleted the fix-text-file-busy branch October 7, 2026 14:23
@JacobStephens2 JacobStephens2 mentioned this pull request Oct 7, 2026
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