astrid-runtime/astrid · error
present migration source has absent digest
Error message
present migration source has absent digest
What it means
`SourceIdentity::from_snapshot_fields` was called with `present=true`, but building the present identity failed because the digest parsed as the absent sentinel — i.e. the digest string does not represent a real blake3 hex value that this constructor accepts as present. A source marked present must have a real digest; present-with-absent-digest is an internal invariant of the migration snapshot.
Solutions
- Either supply the real 64-hex digest or set `present=false` for an absent source (digest then must be absent).
- Fix the snapshot producer so `present` is derived from whether a digest was actually computed.
- Re-generate the migration snapshot rather than patching the digest field by hand.
- Assert in caller code that a digest exists before marking a source present.
Example fix
// before
SourceIdentity::from_snapshot_fields("", entries, bytes, /*present=*/true)?;
// after
SourceIdentity::from_snapshot_fields("", entries, bytes, /*present=*/false)?; // or pass real digest Defensive patterns
Strategy: validation
Validate before calling
fn present_identity(digest: &str, entries: u64, bytes: u64) -> std::io::Result<SourceIdentity> {
if digest.is_empty() {
return SourceIdentity::from_snapshot_fields(digest, entries, bytes, false); // absent
}
SourceIdentity::from_snapshot_fields(digest, entries, bytes, true)
} Type guard
fn digest_is_present(digest: &str) -> bool { !digest.is_empty() } Try / catch
match from_snapshot_fields(digest, entries, bytes, present) {
Err(e) if e.to_string().contains("absent digest") => eprintln!("snapshot inconsistent: present=true but no digest"),
other => other?,
} Prevention
- Derive `present` from whether a digest was actually computed, never hardcode true.
- Generate snapshots only via the provided constructors/serde impls.
- Round-trip validate snapshot JSON before persisting.
When it happens
Trigger: Passing an empty or sentinel/absent digest string together with `present=true` to `from_snapshot_fields` (e.g. snapshot JSON where `digest` is `""` or a placeholder while `present` is `true`).
Common situations: Snapshot writers that defaulted `present=true` while leaving the digest unset; partial serialization bugs; hand-assembled snapshot records during migration tooling development.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- present migration source has absent digest
- source digest must be 64 lowercase hex characters
- absent migration source has a digest
- cannot migrate an Astrid home without a layout-version…
- destination proof is missing its payload
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/3032d3a08d5f9366.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-kernel/src/legacy_migration_barrier/source.rs:209
}
}
pub(super) fn from_snapshot_fields(
digest: &str,
entries: u64,
bytes: u64,
present: bool,
) -> io::Result<Self> {
if !present {
return Ok(Self::absent());
}
Self::present(
SourceDigest::parse(digest.to_owned())
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?,
SourceCount::new(entries),
SourceCount::new(bytes),
)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))
}
}
impl<'de> Deserialize<'de> for SourceIdentity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct Raw {
digest: SourceDigest,
entries: SourceCount,
bytes: SourceCount,
present: bool,
}
let raw = Raw::deserialize(deserializer)?;
SourceIdentity {View on GitHub (pinned to affd8760f4)