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.

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.

Appendix: 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)