libnyanpasu/clash-nyanpasu · error

invalid profile materialization operation id

Error message

invalid profile materialization operation id

What it means

locate_materialization validates the operation_id string before searching journal locations and rejects ids that fail valid_operation_id. Operation ids are fixed-alphabet, fixed-length nanoids; an invalid id means the caller passed arbitrary user input or a corrupted value, and treating it as a path component would risk traversal or lookup errors.

Solutions

  1. Validate the id before calling (same alphabet/length rules as valid_operation_id) and reject bad input at the boundary.
  2. Obtain operation ids only from allocate_operation_id or stored journals, not from free-form user input.
  3. If the id came from persisted state, check it was not truncated or modified during storage/serialization.
  4. Return a clear user-facing 'invalid operation id' message instead of propagating the raw error.

Example fix

// before
client.promote(root, &user_input_id).await?; // arbitrary user string
// after
if !valid_operation_id(&user_input_id) {
    return Err(anyhow!("operation id must be a 16-char SAFE-alphabet nanoid"));
}
client.promote(root, &user_input_id).await?;
Defensive patterns

Strategy: validation

Validate before calling

fn valid_operation_id(id: &str) -> bool {
    id.len() == 16 && id.chars().all(|c| nanoid::alphabet::SAFE.contains(&c))
}
if !valid_operation_id(&user_supplied_id) {
    return Err("invalid operation id");
}

Type guard

fn as_operation_id(input: &str) -> Option<&str> {
    (input.len() == 16 && input.bytes().all(|b| SAFE_ALPHABET.contains(&b))).then_some(input)
}

Try / catch

match client.promote(root, &id).await {
    Err(e) if e.to_string().contains("invalid profile materialization operation id") => {
        // surface a validation error to the user, do not retry
        Err(UserError::BadOperationId)
    }
    r => r,
}

Prevention

When it happens

Trigger: promote/complete/compensate/reconcile called with an operation_id that is empty, contains characters outside the SAFE alphabet, or has the wrong length — typically user-supplied input passed straight from a CLI/UI into the API.

Common situations: Unvalidated frontend/API input reaching the materialization layer; manually edited or truncated journal ids; constructing ids by hand instead of via allocate_operation_id.

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/86f984bc9e1b0653. Report an issue: GitHub.

Appendix: source

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

                &journal,
            )
        })() {
            let _ = Self::remove_private_regular(&Self::stage_file_path(&root, &operation_id));
            let _ = Self::remove_private_regular(&Self::stage_link_path(&root, &operation_id));
            let _ = Self::remove_private_regular(&Self::backup_file_path(&root, &operation_id));
            let _ = Self::remove_private_regular(&Self::backup_link_path(&root, &operation_id));
            return Err(error);
        }
        Ok(PreparedMaterialization::new(operation_id))
    }

    fn locate_materialization(
        &self,
        root: &Path,
        operation_id: &str,
    ) -> anyhow::Result<Option<(JournalLocation, MaterializationJournal)>> {
        if !valid_operation_id(operation_id) {
            bail!("invalid profile materialization operation id");
        }
        let mut found = Vec::new();
        for location in JournalLocation::ALL {
            let path = Self::journal_path(root, location, operation_id);
            match std::fs::symlink_metadata(&path) {
                Ok(metadata) => {
                    if is_symlink_or_reparse(&metadata) || !metadata.is_file() {
                        bail!(
                            "materialization journal is not a regular file: {}",
                            path.display()
                        );
                    }
                    found.push((location, Self::read_journal(&path, operation_id)?, path));
                }
                Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
                Err(error) => {
                    return Err(error)
                        .with_context(|| format!("inspect journal {}", path.display()));

View on GitHub (pinned to f7dbce2997)