Hmbown/CodeWhale · error

lane ` ` was stopped before tmux dry-run start completed

Error message

lane `{}` was stopped before tmux dry-run start completed

What it means

During a tmux dry-run start (the `CODEWHALE_LANE_TMUX_DRY_RUN` test hook), the runtime records the session without spawning a process and then atomically transitions the lane record to Running via `mark_running_if_pending`. If the pending→running transition fails, the lane was concurrently stopped (or is otherwise no longer pending), and the runtime bails rather than overwriting that stop. This protects the invariant that a stop wins over an in-flight start.

Solutions

  1. Retry the start once the concurrent stop has completed — the lane state is consistent, the start just lost the race.
  2. Serialize start/stop for a given lane (per-lane lock or queue) in your orchestration layer so starts and stops cannot interleave.
  3. Check the lane record's status and log to confirm who stopped it; do not force the record back to Running.
  4. If seen without any concurrent stop, inspect the registry record for corruption — status should have been Pending.
Defensive patterns

Strategy: try-catch

Prevention

When it happens

Trigger: Calling `start` with the dry-run env var set while another thread/process stops the lane between the pending record creation and the `mark_running_if_pending` call — a genuine race — or a corrupted record whose status is not Pending at that point.

Common situations: Test/CI harnesses driving dry-run starts concurrently with stop calls; a user issuing `lane stop` while `lane start` is in flight; duplicate start requests for the same lane id where one path already stopped it.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/327cac93b0dd1d8f. Report an issue: GitHub.

Appendix: 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 73e0f67d83)