libnyanpasu/clash-nyanpasu · error

failed to allocate a unique runtime candidate after 16…

Error message

failed to allocate a unique runtime candidate after 16 attempts

What it means

Raised in RuntimeStore::create_candidate_with_names (backend/tauri/src/client/runtime.rs:194) when all 16 pre-generated unique random candidate filenames hit AlreadyExists on exclusive create (create_new(true)). Each attempt uses a fresh nanoid name, so exhausting all 16 implies something is deeply wrong with the candidate directory or the name space is pathologically saturated.

Solutions

  1. Inspect the candidate directory and remove stale candidate-*.yaml files (or call cleanup_stale_candidates).
  2. Verify the candidate directory is a real private directory (not symlinked, not a special FS).
  3. Re-run the operation; a single occurrence is almost certainly not a real collision storm.
  4. If it recurs, check for another process racing to create the same files and coordinate ownership of the directory.

Example fix

// before
store.create_candidate(&bytes).await?;
// after
store.cleanup_stale_candidates(Duration::from_secs(3600)).await?;
store.create_candidate(&bytes).await?;
Defensive patterns

Strategy: retry

Validate before calling

// Before creating candidates, prune stale ones
let removed = store.cleanup_stale_candidates(Duration::from_secs(3600)).await?;
log::debug!("pruned {removed} stale runtime candidates");

Try / catch

match store.create_candidate(&bytes).await {
    Ok(c) => c,
    Err(e) if e.to_string().contains("after 16 attempts") => {
        store.cleanup_stale_candidates(Duration::from_secs(3600)).await?;
        store.create_candidate(&bytes).await?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: create_candidate() loops over 16 nanoid-based names; every OpenOptions::create_new(true) open returns ErrorKind::AlreadyExists and the loop falls through to the bail.

Common situations: An attacker or misbehaving process pre-filling the candidate dir with predictable candidate-*.yaml files; a broken/aliased directory view (e.g. the dir is actually a file or a special filesystem that reports AlreadyExists); astronomically unlikely true nanoid collisions after many stale candidates.

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

Appendix: source

Thrown at backend/tauri/src/client/runtime.rs:194

                    use std::os::unix::fs::OpenOptionsExt;
                    options.mode(0o600);
                }
                match options.open(&path) {
                    Ok(mut file) => {
                        file.write_all(&bytes)?;
                        file.sync_all()?;
                        let bytes_sha256 = Sha256::digest(&bytes).into();
                        return Ok(CandidateFile {
                            path,
                            bytes_sha256,
                            cleaned: false,
                        });
                    }
                    Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => continue,
                    Err(error) => return Err(error.into()),
                }
            }
            anyhow::bail!("failed to allocate a unique runtime candidate after 16 attempts")
        })
        .await?
    }

    pub async fn cleanup_stale_candidates(&self, max_age: Duration) -> anyhow::Result<usize> {
        prepare_private_dir(&self.candidate_dir).await?;
        let now = SystemTime::now();
        let mut removed = 0;
        let mut entries = tokio::fs::read_dir(&self.candidate_dir).await?;
        while let Some(entry) = entries.next_entry().await? {
            let name = entry.file_name();
            if !name.to_string_lossy().starts_with("candidate-") {
                continue;
            }
            let metadata = tokio::fs::symlink_metadata(entry.path()).await?;
            if is_symlink_or_reparse(&metadata) || !metadata.is_file() {
                continue;
            }

View on GitHub (pinned to f7dbce2997)