libnyanpasu/clash-nyanpasu · error
cannot cancel an activated cleanup operation
Error message
cannot cancel an activated cleanup operation
What it means
Cleanup operations in the profile-file transaction framework go through phases (Pending, then Ready once activated/armed). Cancelling is only allowed while the operation is still Pending; once the cleanup has reached the Ready phase it is considered activated and must run (or be retried/compensated), so cancel_cleanup bails with this error to avoid un-arming a cleanup that may already be partially executed.
Source
Thrown at backend/tauri/src/service/profile_file.rs:1768
operation_id,
CleanupPhase::Pending,
CleanupPhase::Ready,
)
.with_context(|| format!("activate cleanup operation {operation_id}"))
}
fn cancel_cleanup(&self, cleanup: &PreparedCleanup) -> anyhow::Result<()> {
let root = self.ensure_materialization_layout()?;
let operation_id = cleanup.operation_id();
match Self::locate_cleanup(&root, operation_id)? {
None => Ok(()),
Some((CleanupPhase::Pending, _)) => Self::remove_private_regular(&Self::cleanup_path(
&root,
CleanupPhase::Pending,
operation_id,
)),
Some((CleanupPhase::Ready, _)) => {
bail!("cannot cancel an activated cleanup operation")
}
}
}
fn retry_cleanup(
&self,
cleanup: &PreparedCleanup,
profiles: &Profiles,
) -> anyhow::Result<CleanupOutcome> {
let root = self.ensure_materialization_layout()?;
let operation_id = cleanup.operation_id();
let Some((phase, journal)) = Self::locate_cleanup(&root, operation_id)? else {
return Ok(CleanupOutcome::AlreadyAbsent);
};
if phase == CleanupPhase::Pending {
self.activate_cleanup(cleanup)?;
}
View on GitHub (pinned to f7dbce2997)
Solutions
- Do not cancel a Ready cleanup: instead let it execute, or use the compensation/retry path to run and clear it
- Poll the journal state before cancelling: only issue cancel when the recorded phase is Pending
- If the cleanup must not run, examine whether a rollback of the promotion (compensate) is the correct operation instead of cancel
- Check for code paths that promote the phase too early (activation race) and serialize cancel against activation under the same lock/actor
Example fix
// before: unconditional cancel
manager.cancel_cleanup(operation_id)?;
// after: only cancel while still pending
let phase = manager.cleanup_phase(operation_id)?;
if phase == CleanupPhase::Pending {
manager.cancel_cleanup(operation_id)?;
} else {
manager.compensate(operation_id)?;
} Defensive patterns
Strategy: validation
Validate before calling
let phase = manager.cleanup_phase(operation_id)?;
if phase != CleanupPhase::Pending {
// cannot cancel; route to compensation instead
return Err(anyhow::anyhow!("cleanup already activated"));
}
manager.cancel_cleanup(operation_id)?; Type guard
fn is_cancellable(phase: CleanupPhase) -> bool {
matches!(phase, CleanupPhase::Pending)
} Try / catch
match manager.cancel_cleanup(op_id) {
Err(e) if e.to_string().contains("cannot cancel an activated cleanup") => {
manager.compensate(op_id)?; // activated: compensate instead
}
other => other?,
} Prevention
- Read the journal phase immediately before cancelling; cancel only Pending operations
- Avoid issuing cancel concurrently with activation; route both through a single serialized owner (actor/lock)
- Design UX so an activated cleanup is presented as 'run or compensate', not 'cancel'
- On shutdown, decide cancel-vs-compensate based on phase before exiting
When it happens
Trigger: Calling cancel_cleanup (the message-based cancel path that dispatches on the (CleanupPhase, _) pair) for an operation whose journal records CleanupPhase::Ready. The match arm Some((CleanupPhase::Ready, _)) unconditionally bails.
Common situations: A race between the activation step (which promotes Pending -> Ready) and a user/tool issuing cancel; double-cancellation attempts after a retry already armed the cleanup; application shutdown while cleanup was armed and then a cancel is issued on restart.
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
- proxy actor is unavailable
- cleanup journal destination is not a regular file: {}
- cleanup journal destination has a different payload
- pending cleanup journal is not a regular file
- ready cleanup journal is not a regular file
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/18d623afb5ba3a4f.
Report an issue: GitHub.