Hmbown/CodeWhale · error
Agent Mail can be canceled only while queued
Error message
Agent Mail can be canceled only while queued (status: {:?}) What it means
Agent Mail can only be canceled while it is still Queued. Once delivery has started (Delivering), completed (Delivered/Read), or been canceled (Canceled), the cancel operation refuses with the current status in the message. This keeps the status machine monotonic — you cannot un-send mail that is already in flight or delivered.
Solutions
- Check the envelope status is Queued before requesting cancellation.
- If the status is Delivered/Read, accept that the mail cannot be recalled and inform the user.
- Handle retries: a canceled mail is idempotently returned as-is, so re-cancel is safe; only in-flight/delivered statuses error.
- Minimize the queue→cancel window by issuing the cancel promptly after send.
Example fix
// before
match mail.status {
AgentMailStatus::Queued => runtime.cancel_agent_mail(thread_id, id).await?,
_ => runtime.cancel_agent_mail(thread_id, id).await?, // may bail
}
// after
if mail.status == AgentMailStatus::Queued {
runtime.cancel_agent_mail(thread_id, id).await?;
} else {
eprintln!("mail already {:?}; cannot cancel", mail.status);
} Defensive patterns
Strategy: validation
Validate before calling
let mail = store.load_agent_mail(message_id)?;
if mail.status != AgentMailStatus::Queued {
anyhow::bail!("cannot cancel: status is {:?}", mail.status);
} Type guard
fn is_cancelable(status: &AgentMailStatus) -> bool { matches!(status, AgentMailStatus::Queued) } Try / catch
match runtime.cancel_agent_mail(thread_id, message_id).await {
Ok(env) => info!("canceled while {:?}", env.status),
Err(e) if e.to_string().contains("only while queued") => info!("mail already left the queue; cannot cancel"),
Err(e) => return Err(e),
} Prevention
- Cancel promptly after sending; the cancelable window ends when delivery starts.
- Disable cancel buttons once status leaves Queued.
- Handle idempotent re-cancel (Canceled status returns Ok) separately from real errors.
When it happens
Trigger: Calling the cancel-agent-mail flow when the envelope's status is Delivering, Delivered, Read, or Canceled.
Common situations: A race where delivery completes between the UI showing 'pending' and the user clicking cancel; retrying a cancel after it already succeeded (status now Canceled — that path returns Ok silently); canceling mail the agent has already started consuming.
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
- Agent Mail can be marked read only after delivery
- Cloud job is and cannot be run; confirm it first with…
- Agent not found
- Cannot queue a parent message for agent
- Cloud agent sandbox entered state
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/f9835accf6def5a3.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:6504
thread_id: &str,
message_id: &AgentMailMessageId,
) -> Result<AgentMailEnvelope> {
let thread = self.get_thread(thread_id).await?;
let address = agent_mail_address(&self.store.owner_id, &thread)?;
let envelope = {
let _mail_mutation = self.store.mail_mutation.lock();
let mut envelope = self.store.load_agent_mail(message_id)?;
if envelope.destination != address {
bail!("Agent Mail ownership denied: message does not belong to this destination");
}
match envelope.status {
AgentMailStatus::Canceled => envelope,
AgentMailStatus::Queued => {
envelope.status = AgentMailStatus::Canceled;
self.store.save_agent_mail(&envelope)?;
envelope
}
_ => bail!(
"Agent Mail can be canceled only while queued (status: {:?})",
envelope.status
),
}
};
self.emit_agent_mail_event(AGENT_MAIL_EVENT_CANCELED, &envelope)
.await?;
Ok(envelope)
}
/// Claim and project one envelope into the existing destination turn
/// queue. A busy thread keeps queued mail untouched; retryable failures are
/// claimed again only below the bounded attempt ceiling.
pub async fn deliver_agent_mail(
&self,
thread_id: &str,
message_id: &AgentMailMessageId,
) -> Result<(AgentMailEnvelope, Option<TurnRecord>)> {View on GitHub (pinned to 73e0f67d83)