{"record":{"id":"2ae7a09662bf2c18","repo":"screenpipe/screenpipe","slug":"recovery-candidate-physical-identity-changed-durin","errorCode":null,"errorMessage":"recovery candidate physical identity changed during verification","messagePattern":"recovery candidate physical identity changed during verification","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/screenpipe-db/src/recovery.rs","lineNumber":383,"sourceCode":"    verify_fts5_indexes(&mut connection).await?;\n    connection\n        .close()\n        .await\n        .context(\"closing final verification connection\")?;\n\n    for suffix in [\"-wal\", \"-shm\"] {\n        let sidecar = sqlite_sidecar(candidate, suffix);\n        if sidecar.exists() {\n            bail!(\n                \"verified candidate left a live SQLite sidecar at {}; refusing a split-generation install\",\n                sidecar.display()\n            );\n        }\n    }\n    let after_identity = sqlite_file_identity(candidate)\n        .with_context(|| format!(\"re-identifying candidate {}\", candidate.display()))?;\n    if after_identity != before_identity {\n        bail!(\"recovery candidate physical identity changed during verification\");\n    }\n    if forbidden_identities.contains(&after_identity) {\n        bail!(\"recovery candidate became the quarantined physical generation\");\n    }\n\n    debug_assert!(initial_quick_rows > 0);\n    debug_assert!(initial_integrity_rows > 0);\n    Ok(RecoveryVerification {\n        file_identity: after_identity,\n        quick_check_rows,\n        integrity_check_rows,\n        foreign_key_violations,\n    })\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;","sourceCodeStart":365,"sourceCodeEnd":401,"githubUrl":"https://github.com/screenpipe/screenpipe/blob/4ebf712990fee17eeaf904dacf749b6e96ac9bf3/crates/screenpipe-db/src/recovery.rs#L365-L401","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"let before = sqlite_file_identity(&candidate)?;\n// ... run verification ...\nlet after = sqlite_file_identity(&candidate)?;\nif before != after { return Err(anyhow!(\"candidate was swapped during verification\")); }","typeGuard":null,"tryCatchPattern":"let mut attempt = 0;\nloop {\n    match verify_fresh_sqlite_recovery_candidate(&candidate, &forbidden).await {\n        Err(e) if e.to_string().contains(\"physical identity changed\") && attempt < 2 => {\n            attempt += 1;\n            exclude_from_sync_tools();\n            re_copy_candidate();\n        }\n        other => break other,\n    }\n}","preventionTips":["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"],"tags":["sqlite","recovery","file-identity","concurrency"],"backgroundTag":"sqlite-recovery-generation-mismatch","analyzedSha":"4ebf712990fee17eeaf904dacf749b6e96ac9bf3","analyzedAt":"2026-09-01T23:33:43.065Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}