Skip to content

Concurrent resumes of the same run double-drive it, and leave the ctl-window record naming the older window #530

Description

@pbean

Found by codex during the review gate on #518, verified by reproduction. Filed rather than fixed there: #518 does not touch cli.py, and the root cause predates it.

The root cause: an unlocked check-then-act on engine liveness

cmd_resume gates on runs.engine_liveness(run_dir) and then launches. Nothing holds a lock across the two steps, and engine.pid is not written until the detached child actually starts — so two resumes of the same paused run can both read a non-alive liveness and both proceed. The result is two engines driving one run dir, which is the outcome the gate's own message exists to prevent:

run <id> is still live — resuming would double-drive it; stop it first

This is on main (cli.py, the live == "alive" branch) and is reachable from both the CLI and the TUI. The TUI widens the window further, because resume/resolve read state, raise a confirm modal, and launch from the callback — a human-length gap between the check and the act.

The symptom codex reported

Downstream of that, the ctl-window record introduced by #518 goes stale. Two launches mint two windows under the same <kind>-<run_id> name and write the record unsynchronized, so the writes can land out of mint order:

launch._record_ctl_window(project, "RID", "@7")   # the LATER window
launch._record_ctl_window(project, "RID", "@5")   # the earlier one lands after
# both windows listed:
ctl_window_id(project, "RID") -> '@5'             # the OLDER window wins

And a launch that fails to record removes the other launch's record wholesale, since the cleanup is unconditional:

launch._record_ctl_window(project, "RID", "@7")
launch._forget_ctl_window(project, "RID")
# record exists: False
ctl_window_id(project, "RID") -> '@5'             # back to listing order

So a attaches to, and x kills, a parked or failed launcher while the other window drives the run.

Why the symptom should not be fixed on its own

Making the record generation-aware would restore correct window targeting for one of two engines that are both writing the same run dir. That is a worse state to be in than an ambiguous attach, because it looks correct. Mint order also cannot be recovered after the fact without either a generation source or the lock that is missing in the first place.

ctl_window_id re-proves the record against the live listing, so a stale record only wins while that window is still listed under the run id — which is exactly the concurrent-launch case.

Suggested shape

Serialize launch-per-run: take an exclusive lock on the run dir across the liveness check and the launch, so the second resume observes the first and refuses. platform_util.file_lock already provides the primitive (an advisory lock released by the kernel if the holder dies) and is used this way by install.py. That fixes the double-drive and makes the record ordering question disappear rather than needing its own mechanism.

Worth deciding at the same time whether the CLI's unknown liveness path (which warns and proceeds, deliberately, so resume stays usable when a pid is unverifiable) should also be serialized — the lock and the liveness verdict answer different questions.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions