{"record":{"id":"f9835accf6def5a3","repo":"Hmbown/CodeWhale","slug":"agent-mail-can-be-canceled-only-while-queued-status","errorCode":null,"errorMessage":"Agent Mail can be canceled only while queued (status: {:?})","messagePattern":"Agent Mail can be canceled only while queued \\(status: (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":6504,"sourceCode":"        thread_id: &str,\n        message_id: &AgentMailMessageId,\n    ) -> Result<AgentMailEnvelope> {\n        let thread = self.get_thread(thread_id).await?;\n        let address = agent_mail_address(&self.store.owner_id, &thread)?;\n        let envelope = {\n            let _mail_mutation = self.store.mail_mutation.lock();\n            let mut envelope = self.store.load_agent_mail(message_id)?;\n            if envelope.destination != address {\n                bail!(\"Agent Mail ownership denied: message does not belong to this destination\");\n            }\n            match envelope.status {\n                AgentMailStatus::Canceled => envelope,\n                AgentMailStatus::Queued => {\n                    envelope.status = AgentMailStatus::Canceled;\n                    self.store.save_agent_mail(&envelope)?;\n                    envelope\n                }\n                _ => bail!(\n                    \"Agent Mail can be canceled only while queued (status: {:?})\",\n                    envelope.status\n                ),\n            }\n        };\n        self.emit_agent_mail_event(AGENT_MAIL_EVENT_CANCELED, &envelope)\n            .await?;\n        Ok(envelope)\n    }\n\n    /// Claim and project one envelope into the existing destination turn\n    /// queue. A busy thread keeps queued mail untouched; retryable failures are\n    /// claimed again only below the bounded attempt ceiling.\n    pub async fn deliver_agent_mail(\n        &self,\n        thread_id: &str,\n        message_id: &AgentMailMessageId,\n    ) -> Result<(AgentMailEnvelope, Option<TurnRecord>)> {","sourceCodeStart":6486,"sourceCodeEnd":6522,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L6486-L6522","documentation":"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.","triggerScenarios":"Calling the cancel-agent-mail flow when the envelope's status is Delivering, Delivered, Read, or Canceled.","commonSituations":"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.","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."],"exampleFix":"// before\nmatch mail.status {\n    AgentMailStatus::Queued => runtime.cancel_agent_mail(thread_id, id).await?,\n    _ => runtime.cancel_agent_mail(thread_id, id).await?, // may bail\n}\n// after\nif mail.status == AgentMailStatus::Queued {\n    runtime.cancel_agent_mail(thread_id, id).await?;\n} else {\n    eprintln!(\"mail already {:?}; cannot cancel\", mail.status);\n}","handlingStrategy":"validation","validationCode":"let mail = store.load_agent_mail(message_id)?;\nif mail.status != AgentMailStatus::Queued {\n    anyhow::bail!(\"cannot cancel: status is {:?}\", mail.status);\n}","typeGuard":"fn is_cancelable(status: &AgentMailStatus) -> bool { matches!(status, AgentMailStatus::Queued) }","tryCatchPattern":"match runtime.cancel_agent_mail(thread_id, message_id).await {\n    Ok(env) => info!(\"canceled while {:?}\", env.status),\n    Err(e) if e.to_string().contains(\"only while queued\") => info!(\"mail already left the queue; cannot cancel\"),\n    Err(e) => return Err(e),\n}","preventionTips":["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."],"tags":["agent-mail","state-machine","lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}