astrid-runtime/astrid · error

corpus label must contain only lowercase ASCII letters…

Error message

corpus label must contain only lowercase ASCII letters, digits, and hyphens

What it means

`validate_label` enforces corpus labels be non-empty strings of only lowercase ASCII letters, digits, and hyphens. This keeps labels safe to embed in report filenames and prevents them from smuggling path components.

Solutions

  1. Rename the corpus to a slug: lowercase letters, digits, hyphens only (e.g. `my-corpus-2024`)
  2. Normalize the label before constructing the corpus: `name.to_lowercase().replace([' ', '_'], "-")`
  3. Reject or auto-slugify user input in your CLI wrapper before passing it to the corpus builder

Example fix

// before
let corpus = Corpus::from_files("My_Corpus/2024", kind, inputs)?;
// after
let label: String = "My_Corpus/2024".to_lowercase().replace([' ', '_', '/'], "-");
let corpus = Corpus::from_files(&label, kind, inputs)?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_corpus_label(label: &str) -> bool {
    !label.is_empty() && label.bytes().all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-')
}
assert!(is_valid_corpus_label(&name), "invalid corpus label: {name}");

Type guard

fn sanitize_label(raw: &str) -> String {
    let s: String = raw.to_lowercase().bytes().filter(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || *b == b'-').map(|b| b as char).collect();
    if s.is_empty() { "corpus".into() } else { s }
}

Try / catch

match Corpus::from_files(&name, kind, inputs) {
    Ok(c) => c,
    Err(e) if e.to_string().contains("corpus label must contain") => {
        let fixed = sanitize_label(&name);
        Corpus::from_files(&fixed, kind, inputs)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Creating a `Corpus` via `from_files`/`from_directory` with a name that is empty, contains uppercase letters, underscores, spaces, slashes, or non-ASCII characters.

Common situations: Deriving corpus names from filenames that contain uppercase/underscores, using absolute paths as labels, user-supplied CLI names, or names built from `to_string_lossy()` of arbitrary paths.

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 astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/2235741ec7b17c6b. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-storage-chunker-evidence/src/corpus.rs:538

        .with_context(|| format!("stat corpus input {}", path.display()))?
        .len();
    let mut reader = HashingReader::new(BufReader::with_capacity(READER_CAPACITY, file));
    std::io::copy(&mut reader, &mut std::io::sink())
        .with_context(|| format!("snapshot corpus input {}", path.display()))?;
    let snapshot = reader.finish();
    if snapshot.logical_bytes != expected_bytes {
        bail!("corpus input changed while its baseline snapshot was captured");
    }
    Ok(snapshot)
}

fn validate_label(label: &str) -> Result<()> {
    if label.is_empty()
        || !label
            .bytes()
            .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-')
    {
        bail!("corpus label must contain only lowercase ASCII letters, digits, and hyphens");
    }
    Ok(())
}

fn validate_relative_git_path(path: &Path) -> Result<()> {
    if path.as_os_str().is_empty()
        || path.is_absolute()
        || path.components().any(|component| {
            matches!(
                component,
                std::path::Component::ParentDir
                    | std::path::Component::RootDir
                    | std::path::Component::Prefix(_)
            )
        })
        || path.to_string_lossy().contains(':')
    {
        bail!("git history path must be a relative in-repository path");

View on GitHub (pinned to affd8760f4)