Hmbown/CodeWhale · error

Cloud job is and cannot be confirmed.

Error message

Cloud job {} is {} and cannot be confirmed.

What it means

confirm_job only transitions a cloud job out of the Proposed status. If the stored job is already in any later state (confirmed, dispatched, failed, cancelled, ...), the confirm call bails with the job's id and its human-readable status label. This is a state-machine guard preventing double confirmation and double dispatch.

Solutions

  1. Reload the job (store.load) and check its status before calling confirm_job; only Proposed jobs are confirmable.
  2. If the job is already confirmed/running, take the idempotent path: query its current outcome instead of confirming again.
  3. If the job is in a terminal state (failed/cancelled), create a new proposed job rather than re-confirming.
  4. Handle the race: if concurrent confirmation is possible, treat this error as 'someone else confirmed first' and re-read state.

Example fix

// before
confirm_job(id, &creds, &token)?;
// after
let job = store.load(id)?;
if job.status == CloudJobStatus::Proposed {
    confirm_job(id, &creds, &token)?;
}
Defensive patterns

Strategy: try-catch

Validate before calling

let job = store.load(id)?;
let confirmable = job.status == CloudJobStatus::Proposed;

Type guard

fn is_confirmable(job: &CloudJob) -> bool {
    job.status == CloudJobStatus::Proposed
}

Try / catch

match confirm_job(id, &creds, &token) {
    Ok(outcome) => outcome,
    Err(e) if e.to_string().contains("cannot be confirmed") => {
        let job = store.load(id)?;
        eprintln!("job {} already {} — fetching current state", job.id, status_label(job.status));
        /* take idempotent read path */
        /* ... */
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling confirm_job (directly or via dispatch) with an id whose persisted job has already been confirmed or has progressed past Proposed — e.g. confirming the same job twice, or re-running a dispatch after a partial failure that advanced the status.

Common situations: Double-clicking / retrying a confirm action in the UI; replaying a queued command after a crash; two sessions confirming the same proposed job concurrently; resuming an old job id from history.

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/fd45343567f3c0ee. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/cloud_dispatch.rs:744

}

/// Confirm a previously proposed job — in place, under the SAME id.
///
/// The record is mutated (`proposed` → `launching`, `confirmed = true`) and
/// saved as itself; a second confirm finds `launching` and refuses. Routing
/// through `execute_dispatch` instead would allocate a fresh job id (its ids
/// hash the plan plus `unix_now()` at second granularity), leaving the
/// proposal re-confirmable without limit — every confirm another sandbox
/// and another PR.
pub fn confirm_job(
    store: &CloudJobStore,
    id: &str,
    credentials: &CredentialState,
    machine_token: &MachineTokenState,
) -> Result<DispatchOutcome> {
    let mut job = store.load(id)?;
    if job.status != CloudJobStatus::Proposed {
        bail!(
            "Cloud job {} is {} and cannot be confirmed.",
            job.id,
            status_label(job.status)
        );
    }
    job.confirmed = true;
    if matches!(credentials, CredentialState::Missing) {
        job.status = CloudJobStatus::Refused;
        job.refusal = Some(missing_credentials_message());
        job.note = missing_credentials_message();
        job.finished_unix = Some(unix_now());
        store.save(&job)?;
        return Ok(DispatchOutcome::Refused(job));
    }
    if matches!(machine_token, MachineTokenState::Missing) {
        job.status = CloudJobStatus::Refused;
        job.refusal = Some(missing_machine_token_message());
        job.note = missing_machine_token_message();

View on GitHub (pinned to 73e0f67d83)