libnyanpasu/clash-nyanpasu · error

invalid profile cleanup operation id

Error message

invalid profile cleanup operation id

What it means

locate_cleanup validates the operation id before probing the filesystem: ids must match the library's strict format (valid_operation_id, typically a fixed charset/length token). If the caller passes a malformed id, the library bails immediately rather than constructing paths from untrusted input. This is an input-validation guard that also prevents path traversal via crafted ids.

Source

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

    fn active_managed_paths(profiles: &Profiles) -> HashSet<ManagedProfilePath> {
        profiles
            .items
            .values()
            .filter_map(|item| {
                item.definition
                    .source()
                    .map(|source| source.materialized().file.clone())
            })
            .collect()
    }

    fn locate_cleanup(
        root: &Path,
        operation_id: &str,
    ) -> anyhow::Result<Option<(CleanupPhase, MaterializationJournal)>> {
        if !valid_operation_id(operation_id) {
            bail!("invalid profile cleanup operation id");
        }
        let pending_path = Self::cleanup_path(root, CleanupPhase::Pending, operation_id);
        let ready_path = Self::cleanup_path(root, CleanupPhase::Ready, operation_id);
        let pending = match std::fs::symlink_metadata(&pending_path) {
            Ok(metadata) if !is_symlink_or_reparse(&metadata) && metadata.is_file() => {
                Some(Self::read_journal(&pending_path, operation_id)?)
            }
            Ok(_) => bail!("pending cleanup journal is not a regular file"),
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,
            Err(error) => return Err(error).context("inspect pending cleanup journal"),
        };
        let ready = match std::fs::symlink_metadata(&ready_path) {
            Ok(metadata) if !is_symlink_or_reparse(&metadata) && metadata.is_file() => {
                Some(Self::read_journal(&ready_path, operation_id)?)
            }
            Ok(_) => bail!("ready cleanup journal is not a regular file"),
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,
            Err(error) => return Err(error).context("inspect ready cleanup journal"),

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Pass the operation id exactly as it was returned by the API that created the cleanup operation; do not trim, rename, or reformat it.
  2. Validate the id yourself before calling: check it is non-empty and matches the expected token pattern (alphanumeric, fixed length).
  3. If recovering from disk, extract the id with the library's own listing/parsing helpers (e.g. list_operation_ids) instead of string-splitting file names.
  4. Reject or sanitize user-supplied ids at the UI/IPC boundary before they reach recovery code.

Example fix

// before
let id = &file_name; // "pending-abc123.journal"
locate_cleanup(root, id)?;
// after
let id = parse_operation_id(&file_name)?; // extract the bare token
if !valid_operation_id(&id) { return Err(anyhow!("bad id")); }
locate_cleanup(root, &id)?;
Defensive patterns

Strategy: validation

Validate before calling

fn valid_operation_id(id: &str) -> bool {
    !id.is_empty()
        && id.len() <= 64
        && id.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')
        && !id.starts_with('-')
}

Try / catch

match locate_cleanup(root, id) {
    Err(e) if e.to_string() == "invalid profile cleanup operation id" => {
        // reject the input at the API boundary; do not retry with the same id
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling the cleanup-lookup/recovery API with an operation id that is empty, contains characters outside the allowed set (path separators, '..', non-ASCII), or has the wrong length — e.g. passing a user-supplied string, a file name parsed loosely from a directory listing, or a truncated id.

Common situations: Feeding a journal file name that still contains a phase prefix or extension into the lookup; logging/telemetry code echoing a user-controlled id into recovery; hand-written tests using ids like "test" or "123" that don't satisfy the format; upgrading versions where id generation changed.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — 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/4221467f29fe4069. Report an issue: GitHub.