Hmbown/CodeWhale · error · anyhow::Error
lane `{}` was stopped before tmux dry-run start completed
Error message
lane `{}` was stopped before tmux dry-run start completed What it means
In the CODEWHALE_LANE_TMUX_DRY_RUN test path, TmuxRuntime::start records a lane_log event and then calls registry.mark_running_if_pending(record). A false return means the lane's status is no longer Pending — a concurrent stop already claimed it — so the start aborts rather than flipping a stopped lane back to Running.
Source
Thrown at crates/lane/src/runtime.rs:729
"workflow": record.workflow,
"fleet": record.fleet,
"issue": record.issue,
"dry_run": dry_run,
}),
)?;
if dry_run {
append_log_event(
&record.log_path,
serde_json::json!({
"type": "lane_log",
"message": "tmux dry-run: session recorded without spawning process",
"command": spec.command,
"cwd": cwd.as_ref().map(|p| p.display().to_string()),
}),
)?;
if !registry.mark_running_if_pending(record)? {
bail!(
"lane `{}` was stopped before tmux dry-run start completed",
record.id
);
}
return Ok(());
}
let log_proxy = spec
.log_proxy
.as_deref()
.context("tmux runtime requires a lane log proxy executable")?;
// Detached session: child output remains an operator journal only.
// Terminal control state goes through a separate bounded, atomically
// renamed receipt so child stdout cannot forge Lane completion.
let receipt_path = lane_exit_receipt_path(&record.log_path);
let receipt_tmp_path = lane_exit_receipt_tmp_path(&record.log_path);
let environment_path = lane_environment_path(&record.log_path);View on GitHub (pinned to 0c42157ee5)
Solutions
- Treat the lane as stopped: refetch its record and assert the terminal state instead of retrying the start
- Serialize start/stop per lane id in tests (await the start's completion before stopping)
- Use distinct lane ids per test to avoid registry contention
Example fix
// before start(lane_id); stop(lane_id); // may win the race in dry-run tests // after start(lane_id).await?; // wait for the Pending->Running transition stop(lane_id).await?;
Defensive patterns
Strategy: try-catch
Try / catch
match runtime.start(®istry, &mut record, &spec) {
Ok(()) => { /* lane running (dry-run recorded) */ }
Err(err) if err.to_string().contains("stopped before") => {
let fresh = registry.get(&lane_id)?; // refetch: lane is stopped, not failed
assert_eq!(fresh.status, LaneStatus::Stopped);
}
Err(err) => return Err(err),
} Prevention
- Await start completion before issuing stop in tests
- Use unique lane ids per test to avoid registry contention
- Treat 'stopped before ...' errors as races, not resource failures — never blind-retry
When it happens
Trigger: Dry-run lane start racing a concurrent stop (or a registry transition) on the same lane id: by the time the Pending->Running compare-and-set runs, the state has moved. Only occurs with CODEWHALE_LANE_TMUX_DRY_RUN set, i.e. in tests.
Common situations: Parallel tests starting and stopping the same lane id; test code issuing stop immediately after start without waiting for the transition; shared registry state across test tasks.
Related errors
- lane `{}` was stopped before tmux launch
- tmux runtime is unavailable: `tmux -V` failed with {}: {}
- tmux has-session for {session} failed with {}: {}
- tmux session {session} remains active after kill-session ({s
- tmux runtime requires a non-empty command
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/327cac93b0dd1d8f.
Report an issue: GitHub.