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

  1. Re-run verification with exclusive, unsynced access to the candidate path
  2. Exclude the recovery directory from file-sync/backup tools during verification
  3. Ensure only one recovery job runs at a time (use a lock file)
  4. 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

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


AI-assisted analysis of screenpipe/screenpipe@4ebf712990 (2026-09-01). Data as JSON: /api/errors/2ae7a09662bf2c18. Report an issue: GitHub.