libnyanpasu/clash-nyanpasu · error

failed to allocate a unique runtime candidate after 16 attem

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.

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)

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.