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
- Reload the job (store.load) and check its status before calling confirm_job; only Proposed jobs are confirmable.
- If the job is already confirmed/running, take the idempotent path: query its current outcome instead of confirming again.
- If the job is in a terminal state (failed/cancelled), create a new proposed job rather than re-confirming.
- 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
- Check job.status == Proposed before confirming.
- Design confirm actions as idempotent: re-read state instead of retrying blindly.
- Disable the confirm control in the UI once a job leaves Proposed.
- Guard against concurrent confirms (single-writer or optimistic check).
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
- Trigger ' ' cannot be canceled (status: )
- Agent Mail can be canceled only while queued
- Agent Mail can be marked read only after delivery
- AlreadyExists
- Cloud agent create failed
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)