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
- Validate the id before calling (same alphabet/length rules as valid_operation_id) and reject bad input at the boundary.
- Obtain operation ids only from allocate_operation_id or stored journals, not from free-form user input.
- If the id came from persisted state, check it was not truncated or modified during storage/serialization.
- 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
- Only pass ids obtained from allocate_operation_id or stored journals.
- Validate ids at the CLI/UI boundary before they reach service APIs.
- Never build operation ids from user free-text.
- Persist ids verbatim and avoid truncation in serialization.
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
- clean-schema output failed validation
- hotkey_error.invalid_hotkey
- hotkey_error.missing_super_key
- invalid filter: missing `when`
- invalid filter: no action
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)