Hmbown/CodeWhale · error · anyhow::Error

terminal lane transition requires a terminal status

Error message

terminal lane transition requires a terminal status

What it means

Thrown by LaneRegistry::mark_terminal_if_active_fenced when the status argument is an active status. The API exists to move a lane from an active state (pending/running) to a terminal one; LaneStatus::is_active() covers Pending and Running, so passing either bails immediately, before any locking happens. Valid terminal statuses are stopped, failed, and completed.

Source

Thrown at crates/lane/src/registry.rs:378

    /// A pre-lock check is a TOCTOU: another process can transition the record
    /// between the caller's read and this write, and the caller would then act
    /// on a generation it never observed. Checking here means a stale fence
    /// refuses without running `before_transition`, so no backend teardown
    /// happens for a run the caller did not actually target.
    ///
    /// [`mark_terminal_if_active_with`]: Self::mark_terminal_if_active_with
    pub fn mark_terminal_if_active_fenced<F>(
        &self,
        record: &mut LaneRecord,
        status: LaneStatus,
        expected_lifecycle_seq: Option<u64>,
        before_transition: F,
    ) -> Result<TerminalTransition>
    where
        F: FnOnce(&LaneRecord) -> Result<()>,
    {
        if status.is_active() {
            bail!("terminal lane transition requires a terminal status");
        }

        let lock_path = self.root.join(format!("{}.lock", record.id));
        let lock_file = OpenOptions::new()
            .create(true)
            .truncate(false)
            .read(true)
            .write(true)
            .open(&lock_path)
            .with_context(|| format!("open lane lock {}", lock_path.display()))?;
        let mut lock = fd_lock::RwLock::new(lock_file);
        let _guard = lock
            .write()
            .with_context(|| format!("lock lane record {}", record.id))?;

        let mut current = self.load(&record.id)?;
        // Fence first: a mismatched generation must not run backend teardown.
        if let Some(expected) = expected_lifecycle_seq

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Pass a terminal status: LaneStatus::Completed, LaneStatus::Failed, or LaneStatus::Stopped
  2. To stop a running lane, use LaneStatus::Stopped; to record success/failure use Completed/Failed
  3. If the input status is dynamic, gate the call with !status.is_active() and route active statuses to the appropriate non-terminal update path

Example fix

// before
registry.mark_terminal_if_active_fenced(&mut record, LaneStatus::Running, None, |_| Ok(()))?;

// after
registry.mark_terminal_if_active_fenced(&mut record, LaneStatus::Stopped, None, |_| Ok(()))?;
Defensive patterns

Strategy: type-guard

Validate before calling

if !matches!(status, LaneStatus::Pending | LaneStatus::Running) {
    let transition = registry.mark_terminal_if_active_fenced(&mut record, status, expected_seq, before)?;
} else {
    // active statuses go through their own update path
}

Type guard

fn is_terminal_status(status: LaneStatus) -> bool {
    matches!(status, LaneStatus::Stopped | LaneStatus::Failed | LaneStatus::Completed)
}

Try / catch

match registry.mark_terminal_if_active_fenced(&mut record, status, seq, before) {
    Ok(t) => t,
    Err(err) if err.to_string().contains("requires a terminal status") => {
        anyhow::bail!("bug: routed active status {status:?} into terminal transition");
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: Calling mark_terminal_if_active_fenced(&mut record, LaneStatus::Running, ...) — typically a wrapper that maps an incoming status string/enum straight into the call, or code that confuses 'set status' semantics with 'finish the lane' semantics.

Common situations: Generic status-update adapters feeding arbitrary LaneStatus values into the terminal-transition API; restart flows that try to re-mark a lane as running through the terminal path instead of the start path.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/e007d58c1d7ebd78. Report an issue: GitHub.