Hmbown/CodeWhale · error · anyhow::Error
Trigger '{trigger_id}' cannot be canceled (status: {:?})
Error message
Trigger '{trigger_id}' cannot be canceled (status: {:?}) What it means
cancel_trigger_for_owner loads the record through the owner-scoped lookup and then refuses to cancel anything whose status is not Pending. DelayedTriggerStatus is a one-way state machine (Pending -> Fired | Canceled | Failed), so this error means the trigger already left the pending state; the message embeds the actual status so callers can tell 'already fired' from 'already canceled' or 'failed to enqueue'.
Source
Thrown at crates/tui/src/automation_manager.rs:1460
owner_session_id: &str,
) -> Result<Vec<DelayedTriggerRecord>> {
let mut records = self.list_triggers(status_filter, None)?;
records.retain(|record| record.owner_session_id.as_deref() == Some(owner_session_id));
if let Some(limit) = limit {
records.truncate(limit);
}
Ok(records)
}
/// Cancel a pending trigger owned by the given session.
pub fn cancel_trigger_for_owner(
&self,
trigger_id: &str,
owner_session_id: &str,
) -> Result<DelayedTriggerRecord> {
let mut record = self.get_trigger_for_owner(trigger_id, owner_session_id)?;
if !matches!(record.status, DelayedTriggerStatus::Pending) {
bail!(
"Trigger '{trigger_id}' cannot be canceled (status: {:?})",
record.status
);
}
record.status = DelayedTriggerStatus::Canceled;
self.save_trigger(&record)?;
Ok(record)
}
/// Return all pending triggers whose `fire_at` is at or before `now`.
pub fn collect_due_triggers(&self, now: DateTime<Utc>) -> Result<Vec<DelayedTriggerRecord>> {
let pending = self.list_triggers(Some(DelayedTriggerStatus::Pending), None)?;
Ok(pending
.into_iter()
.filter(|trigger| trigger.owner_session_id.is_some() && trigger.fire_at <= now)
.collect())
}
}View on GitHub (pinned to 0c42157ee5)
Solutions
- Treat the error as information, not a retryable failure: re-read the record with get_trigger_for_owner and show its terminal state.
- Make cancel idempotent in the caller by only attempting it when the fetched status is still Pending.
- If the trigger fired unexpectedly early, inspect the enqueued task instead of retrying the cancel.
Example fix
// before
let record = manager.cancel_trigger_for_owner(trigger_id, session_id)?;
// after: only cancel what is still pending
let current = manager.get_trigger_for_owner(trigger_id, session_id)?;
let record = if matches!(current.status, DelayedTriggerStatus::Pending) {
manager.cancel_trigger_for_owner(trigger_id, session_id)?
} else {
current // already fired, canceled, or failed
}; Defensive patterns
Strategy: validation
Validate before calling
let record = manager.get_trigger_for_owner(trigger_id, session_id)?;
if !matches!(record.status, DelayedTriggerStatus::Pending) {
return Ok(record); // nothing left to cancel
}
manager.cancel_trigger_for_owner(trigger_id, session_id) Prevention
- Refresh the trigger status immediately before confirming a cancel.
- Disable cancel affordances once status leaves Pending.
- Never auto-retry a cancel that failed with a status error.
When it happens
Trigger: Calling cancel_trigger_for_owner on a trigger that collect_due_triggers has already fired; submitting the same cancel twice; canceling a trigger whose enqueue previously failed (status Failed).
Common situations: Double-clicking a cancel button or duplicating a UI action; retrying a cancel request that actually succeeded; the due-trigger scheduler firing between the user opening a confirmation dialog and confirming.
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
- terminal lane transition requires a terminal status
- lane `{}` was stopped before tmux dry-run start completed
- lane `{}` was stopped before tmux launch
- lane `{}` was stopped before inline start completed
- Unable to compute next HOURLY run for BYDAY filter
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/9067e9795840b1e2.
Report an issue: GitHub.