astrid-runtime/astrid · error · anyhow::Error
revocation KV value for
Error message
revocation KV value for {key:?} has {} bytes; expected 8 What it means
decode_epoch converts a revocation KV value into a u64 (little-endian epoch seconds) and requires exactly 8 bytes. If the stored value for the key has any other length, the try_into::<[u8;8]>() fails and this error reports the actual byte count. It indicates a corrupt or foreign entry in the revocation KV namespace.
Solutions
- Inspect the offending KV entry and rewrite it as an 8-byte little-endian u64 epoch
- Identify which writer produced the non-8-byte value (older version / external tool) and fix its encoding
- Delete the corrupt entry if the revocation can be re-derived, then re-record it
- Check for recent version changes in how revocation epochs are serialized
Example fix
// before store.put(key, serde_json::to_vec(&epoch)?)?; // writes JSON text, not 8 bytes // after store.put(key, epoch.to_le_bytes())?; // exactly 8 bytes, LE u64
Defensive patterns
Strategy: validation
Validate before calling
fn is_epoch_value(bytes: &[u8]) -> bool { bytes.len() == 8 }
// skip/log entries that fail is_epoch_value before calling record_*_max Type guard
fn as_epoch(bytes: &[u8]) -> Option<u64> {
if bytes.len() == 8 { Some(u64::from_le_bytes(bytes.try_into().ok()?)) } else { None }
} Try / catch
match revocations::load_from_store(&store).await {
Ok(state) => state,
Err(e) if e.to_string().contains("expected 8") => {
log::error!("corrupt revocation KV entry: {e}; rebuild or delete the entry");
Err(e)
}
Err(e) => return Err(e),
} Prevention
- Always write revocation epochs with u64::to_le_bytes
- Add a store-level unit test asserting value length == 8 after writes
- Reject/log foreign writers that put text/JSON into the revocation namespace
When it happens
Trigger: Calling decode_epoch (via record_principal_max, record_device_max, or load_from_store) on a KV value whose length != 8 — e.g. a value written by a different format/version, a JSON or text value instead of the raw 8-byte LE u64, or truncated/corrupted storage.
Common situations: Manual edits or imports of KV entries with wrong encoding; an older gateway version writing a different serialization; corrupted store after a failed migration; someone storing JSON like '"1700000000"' instead of raw bytes.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- ASTRID_ENFORCED_DISTRO must contain a valid UTF-8 distro…
- Astrid volume record checksum mismatch
- ASTRID_WORKSPACE_STATE_DIR must be valid UTF-8
- capsule path is not valid UTF-8
- capsule projection path is not UTF-8
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/8f8a9e7906590172.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-gateway/src/revocations.rs:104
.open(path)
.with_context(|| format!("open legacy revocation file {}", path.display()))?
};
#[cfg(not(unix))]
let file = std::fs::File::open(path)
.with_context(|| format!("open legacy revocation file {}", path.display()))?;
let mut bytes = Vec::new();
file.take(MAX_REVOCATIONS_FILE_BYTES.saturating_add(1))
.read_to_end(&mut bytes)
.with_context(|| format!("read legacy revocation file {}", path.display()))?;
if bytes.len() as u64 > MAX_REVOCATIONS_FILE_BYTES {
anyhow::bail!("legacy revocation file exceeds migration cap");
}
Ok(bytes)
}
fn decode_epoch(bytes: &[u8], key: &str) -> anyhow::Result<u64> {
let raw: [u8; 8] = bytes.try_into().map_err(|_| {
anyhow::anyhow!(
"revocation KV value for {key:?} has {} bytes; expected 8",
bytes.len()
)
})?;
Ok(u64::from_le_bytes(raw))
}
fn encode_epoch(epoch: u64) -> Vec<u8> {
epoch.to_le_bytes().to_vec()
}
/// Record the maximum principal revocation epoch durably. The returned value
/// is the epoch now authoritative in storage (which may be newer than the
/// requested event when another writer won the CAS race).
pub async fn record_principal_max(
store: &dyn KvStore,
principal: &PrincipalId,
epoch: u64,View on GitHub (pinned to affd8760f4)