screenpipe/screenpipe · error · anyhow::Error
recovery candidate physical identity changed during verifica
Error message
recovery candidate physical identity changed during verification
What it means
verify_fresh_sqlite_recovery_candidate snapshots the candidate's physical identity before running checks and re-reads it at the end; if the identity (device+inode) changed, something replaced the file underneath the verifier and the result is untrustworthy, so it bails. This detects the candidate being swapped mid-verification.
Source
Thrown at crates/screenpipe-db/src/recovery.rs:383
verify_fts5_indexes(&mut connection).await?;
connection
.close()
.await
.context("closing final verification connection")?;
for suffix in ["-wal", "-shm"] {
let sidecar = sqlite_sidecar(candidate, suffix);
if sidecar.exists() {
bail!(
"verified candidate left a live SQLite sidecar at {}; refusing a split-generation install",
sidecar.display()
);
}
}
let after_identity = sqlite_file_identity(candidate)
.with_context(|| format!("re-identifying candidate {}", candidate.display()))?;
if after_identity != before_identity {
bail!("recovery candidate physical identity changed during verification");
}
if forbidden_identities.contains(&after_identity) {
bail!("recovery candidate became the quarantined physical generation");
}
debug_assert!(initial_quick_rows > 0);
debug_assert!(initial_integrity_rows > 0);
Ok(RecoveryVerification {
file_identity: after_identity,
quick_check_rows,
integrity_check_rows,
foreign_key_violations,
})
}
#[cfg(test)]
mod tests {
use super::*;View on GitHub (pinned to 4ebf712990)
Solutions
- Re-run verification with exclusive, unsynced access to the candidate path
- Exclude the recovery directory from file-sync/backup tools during verification
- Ensure only one recovery job runs at a time (use a lock file)
- Rebuild the candidate and verify again if the file may have been mutated
Defensive patterns
Strategy: retry
Validate before calling
let before = sqlite_file_identity(&candidate)?;
// ... run verification ...
let after = sqlite_file_identity(&candidate)?;
if before != after { return Err(anyhow!("candidate was swapped during verification")); } Try / catch
let mut attempt = 0;
loop {
match verify_fresh_sqlite_recovery_candidate(&candidate, &forbidden).await {
Err(e) if e.to_string().contains("physical identity changed") && attempt < 2 => {
attempt += 1;
exclude_from_sync_tools();
re_copy_candidate();
}
other => break other,
}
} Prevention
- Use a lock file so only one recovery job touches the candidate
- Pause Dropbox/OneDrive/iCloud/Time Machine for the recovery directory
- Avoid rename-based writers (editors, copy jobs) during verification
- Run verification to completion without interrupting the process
When it happens
Trigger: sqlite_file_identity(candidate) after verification differs from before_identity — another process deleted and recreated the candidate file, a rebuild tool rewrote it in place via rename, or a sync/backup tool swapped the file while checks were running.
Common situations: Two recovery jobs racing on the same candidate path; Dropbox/OneDrive/iCloud syncing the recovery folder mid-check; a script that re-copies the candidate on a timer; editors or DB tools doing atomic-rename saves over the candidate.
Related errors
- recovery candidate {} reuses the quarantined physical file i
- recovery candidate became the quarantined physical generatio
- {query} failed: {}
- FTS rebuild candidate refused DELETE journal mode: {journal_
- recovery candidate refused DELETE journal mode: {journal_mod
AI-assisted analysis of screenpipe/screenpipe@4ebf712990 (2026-09-01).
Data as JSON: /api/errors/2ae7a09662bf2c18.
Report an issue: GitHub.