libnyanpasu/clash-nyanpasu · error

cleanup tombstone already exists: {}

Error message

cleanup tombstone already exists: {}

What it means

On Windows, destructive profile cleanup first records a tombstone file (cleanup_tombstone_path) marking the operation as in-progress so a crash can't skip the deletion. Before creating it, the code checks the tombstone doesn't already exist; if it does, the operation bails. A pre-existing tombstone means another cleanup with the same operation_id is (or was) already running, and proceeding could double-delete or race.

Source

Thrown at backend/tauri/src/service/profile_file.rs:1473

                    removed += 1;
                }
            }
        }
        Ok(removed)
    }

    fn remove_cleanup_target(
        &self,
        root: &Path,
        operation_id: &str,
        target: &Path,
    ) -> anyhow::Result<()> {
        #[cfg(windows)]
        {
            let tombstone = Self::cleanup_tombstone_path(root, operation_id);
            match std::fs::symlink_metadata(&tombstone) {
                Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
                Ok(_) => bail!("cleanup tombstone already exists: {}", tombstone.display()),
                Err(error) => {
                    return Err(error).with_context(|| {
                        format!("inspect cleanup tombstone {}", tombstone.display())
                    });
                }
            }
            self.ensure_managed_parent(target)?;
            Self::ensure_replaceable_target(target)?;
            replace_atomic(target, &tombstone).with_context(|| {
                format!(
                    "move cleanup target {} to tombstone {}",
                    target.display(),
                    tombstone.display()
                )
            })
        }
        #[cfg(not(windows))]
        {

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Remove the stale tombstone at the reported path only after confirming no cleanup process is currently running, then retry.
  2. Prefer starting the cleanup with a fresh operation_id so the old tombstone is simply ignored by the new attempt.
  3. Serialize cleanup calls in your application (one at a time) to avoid two attempts sharing an id.
  4. If a previous cleanup is actually mid-flight, wait for it to finish instead of deleting its tombstone.

Example fix

// before: retry reuses the old id and hits the tombstone
run_cleanup(root, &old_id)?;
// after: new attempt, new id (old tombstone no longer conflicts)
let id = generate_operation_id();
run_cleanup(root, &id)?;
// or, once certain nothing is running:
let _ = std::fs::remove_file(cleanup_tombstone_path(root, &old_id));
Defensive patterns

Strategy: validation

Validate before calling

fn tombstone_free(root: &Path, id: &str) -> bool {
    match std::fs::symlink_metadata(cleanup_tombstone_path(root, id)) {
        Err(e) => e.kind() == std::io::ErrorKind::NotFound,
        Ok(_) => false,
    }
}

Try / catch

match run_cleanup(root, id) {
    Err(e) if e.to_string().contains("cleanup tombstone already exists") => {
        // confirm no cleanup is running, then remove the stale tombstone
        // or restart the attempt with a fresh operation_id
    }
    other => other,
}

Prevention

When it happens

Trigger: Starting (or retrying) a profile cleanup whose Windows tombstone file for the same operation_id already exists — a previous attempt with the same id crashed before removing the tombstone, two concurrent cleanups were launched with the same id, or a leftover tombstone from a test run.

Common situations: Retrying a failed cleanup without regenerating the operation id; parallel UI actions triggering cleanup twice; interrupted runs on Windows where the tombstone was written but cleanup never completed; stale artifacts left by manual testing.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/b910e9db9eb106bc. Report an issue: GitHub.