Found while fixing #405 (PR #440); verified against the code at 49ad1bed.
Summary
A Ctrl-C in the REPL during a long host operation kills the mtui process outright, and the operation lock (/var/lock/mtui.lock) it had taken on every host stays behind with a dead pid — so unlike #405's in-process strand, the debris reads as foreign (is_mine() requires user AND pid to match) and neither a later mtui run nor session teardown can release it non-forced. It blocks every other tester on those hosts until someone runs unlock --force or the 24h stale reap (lock_reap_stale/lock_stale_age, locks.rs:381-404) fires on their next claim attempt.
Mechanism
- reedline observes
Signal::CtrlC only during read_line (crates/mtui-cli/src/repl.rs:189-195 — "abort the current input and reprompt"); dispatch runs after read_line returns, awaited inline.
- Nothing installs a process-level SIGINT handler.
crates/mtui-core/src/commands/request_review.rs:223-225 documents this explicitly: "Nothing in the CLI installs a SIGINT handler today, so without this a Ctrl-C during a watch kills the process outright" — its sleep_or_interrupt is a bespoke, one-command mitigation, as is regenerate.rs:136's.
- So the default SIGINT disposition terminates the process mid-
update/install/prepare/run, between the flow's lock and its "unlock always" section. There is no Drop-based release and cannot be (the release is an SSH round-trip; Drop cannot await — the same reasoning as provider.rs:355-357 for pool claims).
The cooperative-cancellation seam (Session's CancellationToken, checked at the flows' step boundaries) already exists and is exactly what a SIGINT handler should trigger — today the REPL never cancels it; only MCP job_cancel does.
Suggested direction
Install a SIGINT handler for the duration of a dispatched command (restore reedline's behaviour between commands): first Ctrl-C cancels the session token — the flows unwind at their checkpoints and run their own unlock discipline, exactly like a cooperative MCP job_cancel; a second Ctrl-C (or a short deadline) may hard-exit, accepting the stranded-lock outcome the first stage exists to avoid. request_review's and regenerate's bespoke hooks could then fold into the general mechanism.
Acceptance
Found while fixing #405 (PR #440); verified against the code at
49ad1bed.Summary
A Ctrl-C in the REPL during a long host operation kills the mtui process outright, and the operation lock (
/var/lock/mtui.lock) it had taken on every host stays behind with a dead pid — so unlike #405's in-process strand, the debris reads as foreign (is_mine()requires user AND pid to match) and neither a later mtui run nor session teardown can release it non-forced. It blocks every other tester on those hosts until someone runsunlock --forceor the 24h stale reap (lock_reap_stale/lock_stale_age,locks.rs:381-404) fires on their next claim attempt.Mechanism
Signal::CtrlConly duringread_line(crates/mtui-cli/src/repl.rs:189-195— "abort the current input and reprompt"); dispatch runs afterread_linereturns, awaited inline.crates/mtui-core/src/commands/request_review.rs:223-225documents this explicitly: "Nothing in the CLI installs a SIGINT handler today, so without this a Ctrl-C during a watch kills the process outright" — itssleep_or_interruptis a bespoke, one-command mitigation, as isregenerate.rs:136's.update/install/prepare/run, between the flow's lock and its "unlock always" section. There is noDrop-based release and cannot be (the release is an SSH round-trip;Dropcannot await — the same reasoning asprovider.rs:355-357for pool claims).The cooperative-cancellation seam (
Session'sCancellationToken, checked at the flows' step boundaries) already exists and is exactly what a SIGINT handler should trigger — today the REPL never cancels it; only MCPjob_canceldoes.Suggested direction
Install a SIGINT handler for the duration of a dispatched command (restore reedline's behaviour between commands): first Ctrl-C cancels the session token — the flows unwind at their checkpoints and run their own unlock discipline, exactly like a cooperative MCP
job_cancel; a second Ctrl-C (or a short deadline) may hard-exit, accepting the stranded-lock outcome the first stage exists to avoid.request_review's andregenerate's bespoke hooks could then fold into the general mechanism.Acceptance
update/prepare/install/runstops the flow at a checkpoint, runs the flow's unlock path, and returns to the prompt with a truthful "cancelled" reportrequest_review/regeneratebespoke hooks reconciled with the general handler