Skip to content

Start a child Run from the running binary, not its install path - #265

Merged
JacobStephens2 merged 3 commits into
mainfrom
issue-261
Oct 1, 2026
Merged

JacobStephens2 merged 3 commits into
mainfrom
issue-261

Conversation

@JacobStephens2

@JacobStephens2 JacobStephens2 commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

A child Run (a Ticket's Run in a Spec run, a Base fix) is started from the binary the parent is running, not from the path that binary was installed at.

 child_run::start
-  Command::new(current_exe())        # Linux: "<install path>/thirdshift (deleted)" once replaced → ENOENT
+  Command::new(own_executable())
+    Linux:      /proc/self/exe       # the kernel's link: the parent's own binary, replaced or removed
+    elsewhere:  current_exe()        # as before
+  arg0 = the parent's argv[0]        # Linux only
-  "could not start the Run for #248: No such file or directory (os error 2)"
+  "could not start the Run for #248 from /proc/self/exe (<install path>/thirdshift (deleted)): …"

 main
+  child_run::name_this_process()     # Linux: prctl(PR_SET_NAME, basename(argv[0]))

name_this_process is not in the brief. A process started through /proc/self/exe is named exe by the kernel, so without it pgrep thirdshift, pkill thirdshift and top would stop seeing every child Run on Linux. It runs first thing in every thirdshift process and is a no-op off Linux.

The README's Updating section and the update module's doc comment now say the old binary also serves the child Runs started later on Linux, and that on macOS a child Run started after an update runs the new binary.

Seams tested: the compiled thirdshift binary through the tests' Scenario harness, and the child Run module's start error (start_from, private to the module).

Evidence

  • Before: the three new end-to-end tests, run against the old start():

    test a_run_whose_thirdshift_was_removed_after_it_started_still_starts_its_base_fix ... FAILED
      thirdshift: could not start the Run for #8: No such file or directory (os error 2)
    test a_tickets_run_started_after_thirdshift_was_replaced_runs_the_spec_runs_own_binary ... FAILED
    test a_tickets_run_started_after_thirdshift_was_replaced_can_still_start_its_base_fix ... FAILED
      thirdshift: could not start the Run for #22: No such file or directory (os error 2)
    

    and, with /proc/self/exe but no name_this_process, the name of CI watching, CI-fix Repair and the Repair cap #22's Run: left: "exe\n" right: "thirdshift\n".

    After: cargo fmt --check and cargo clippy --all-targets -- -D warnings clean; cargo test: 28 suites, 577 tests passed, 0 failed.

run a copy of the built binary on Spec #20 (#21, then #22 blocked by #21)
  #21's session lands, then waits
  rename a script over the copy        # or remove it, for the Run with a Base fix
  #21's session ends → the Spec run starts #22
assert exit 0, #22 merged into the Spec branch
assert the script never ran           # #22 ran the Spec run's own binary
assert /proc/<#22's Run>/comm == "thirdshift"

The second Spec run test has #22, started after the replacement, meet an Inherited failure and start its Base fix: a child started through the link starting a child of its own. All three are Linux-only.

Not run here: the macOS build and tests. No macOS target is installed on this machine; CI's test-macos job covers it.

One flake seen and fixed along the way: one of the new tests failed once in a full-suite run at the harness's spawn() of the copied binary. I did not capture the error; the likely cause is "Text file busy" from the copy being open for writing in the test process while another test forked. The harness now copies with cp, and spec_run and base_fix then passed three times in a row plus the full suite.

Unaddressed findings

Standards

  • name_this_process widens the child Run module's interface with an ordering constraint (main must call it first, in every process). Kept: the name can only be set from inside the child, and the reason for it lives in the child Run module.
  • Possible Divergent Change: the child Run module now also owns process naming. Kept there for locality, since the link that causes the wrong name is that module's choice.
  • The Linux condition is written two ways (cfg! in own_executable and start_from, #[cfg] in name_this_process). libc::prctl doesn't exist on macOS, so that one must be compiled out; the others read better as plain ifs. The repeated args_os().next() was addressed with own_command().

Spec

  • name_this_process and arg0 go beyond "what it passes to Command::new is what changes". Kept, for the reason in the Summary; arg0 is now Linux-only, so macOS's argv[0] is as it was.
  • On Linux the real cause wording (from /proc/self/exe (…)) is asserted by no test. A spawn through /proc/self/exe can't be made to fail on demand; the unit tests cover the same code with a missing path and with a dangling link.

Merge Danger

Door: two-way

Reverting the commit restores the old spawn; nothing is persisted or migrated.

Blast Radius: child-Runs

Every Ticket's Run and Base fix on Linux now starts through /proc/self/exe, so a host with no /proc mounted could not start one (current_exe() reads the same link on Linux, so it failed there before too). Every thirdshift process on Linux now sets its own process name from argv[0], which changes what pgrep/top show only if it was started under an argv[0] that differs from its file name.

Closes #261

🤖 Generated with Claude Code

JacobStephens2 and others added 3 commits October 1, 2026 12:19
On Linux a child Run (a Ticket's Run in a Spec run, a Base fix) is now
started through /proc/self/exe, so a Spec run or a Run whose installed
binary was replaced or removed after it started can still start its
children, and they run its own version. The child keeps the parent's
name for ps, pgrep and top, and a child that can't be started names the
executable that was tried.

Closes #261

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, share the test helpers

A child Run that can't be started names the file its link leads to as
well as the link. The child's argv[0] is only set on Linux, where the
child is started through a link. The harness copies the binary with cp,
so the copy is never open for writing in the test process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit 362b9ca into main Oct 1, 2026
13 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-261 branch October 1, 2026 12:43
@JacobStephens2 JacobStephens2 mentioned this pull request Oct 2, 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.

A Spec run can't start a Ticket's Run after the thirdshift binary is replaced mid-run

1 participant