astrid-runtime/astrid · error

too many metadata mutations

Error message

too many metadata mutations

What it means

encode_metadata_mutations serializes the mutation count as a single little-endian u16, so a transaction is hard-capped at 65535 entries. commit_metadata already rejects >1024, so this error means the encoding guard caught a batch that slipped past that validation (or the code was called directly).

Solutions

  1. Route all metadata commits through commit_metadata, which enforces the 1..=1024 bound before encoding.
  2. Split the mutation list into batches of at most 1024 before encoding.
  3. Add a length assertion in tests if you call the encoder directly.

Example fix

// before
let bytes = encode_metadata_mutations(&huge_batch)?;
// after
for chunk in huge_batch.chunks(1024) {
    let bytes = encode_metadata_mutations(chunk)?;
    /* write */
}
Defensive patterns

Strategy: validation

Validate before calling

fn encodable(mutations: &[VolumeMetadataMutation]) -> bool {
    mutations.len() <= u16::MAX as usize
}

Prevention

When it happens

Trigger: Calling encode_metadata_mutations with a slice longer than u16::MAX (65535); calling the encoder directly with an oversized batch, bypassing commit_metadata's 1024-mutation check.

Common situations: Directly invoking the internal encoder in tests or tooling; a future change lowering MAX_METADATA_MUTATIONS consistency or raising it above 65535 so the encoder overflows.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/b168bdff18d4e634. Report an issue: GitHub.

Appendix: source

Thrown at crates/astrid-storage/src/volume/hosted/mod.rs:500

                source,
                destination,
            } => {
                if !regions.contains_key(destination) {
                    return Err(invalid_transition("metadata replace destination is absent"));
                }
                let source_state = regions
                    .remove(source)
                    .ok_or_else(|| invalid_transition("metadata replace source is absent"))?;
                regions.insert(destination.clone(), source_state);
            },
        }
    }
    Ok(())
}

fn encode_metadata_mutations(mutations: &[VolumeMetadataMutation]) -> io::Result<Vec<u8>> {
    let count = u16::try_from(mutations.len())
        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "too many metadata mutations"))?;
    let mut output = Vec::new();
    output.extend_from_slice(&count.to_le_bytes());
    for mutation in mutations {
        let (kind, source, destination) = match mutation {
            VolumeMetadataMutation::Rename {
                source,
                destination,
            } => (1_u8, source, destination),
            VolumeMetadataMutation::Replace {
                source,
                destination,
            } => (2_u8, source, destination),
        };
        output.push(kind);
        for region in [source, destination] {
            let bytes = region.as_str().as_bytes();
            let length = u16::try_from(bytes.len()).map_err(|_| {
                io::Error::new(io::ErrorKind::InvalidInput, "metadata region name too long")

View on GitHub (pinned to affd8760f4)