{"solution_id":"repairing-persisted-path-alias-drift","schema_version":1,"locale":"en","slug":"repairing-persisted-path-alias-drift","title":"When a File Exists but Archiving Says It Does Not","description":"A guarded, reversible recovery for a local task store whose persisted physical path no longer matched the application's logical home alias.","date_published":"2026-08-21","date_modified":"2026-08-21","tags":["windows","sqlite","path-resolution","troubleshooting","data-integrity"],"categories":["DevOps"],"structure_source":"legacy-derived","completeness":"partial","canonical_url":"https://fichil.com/blog/repairing-persisted-path-alias-drift/","alternate_locale_url":"https://fichil.com/zh-cn/blog/repairing-persisted-path-alias-drift/","problem":"The visible error supported several possible explanations: a missing transcript, insufficient permissions, database corruption, or a stale path. Read only checks narrowed the scope before any repair: the target row was present and remained unarchived; the transcript existed exactly once and was not being written; a database quick check returned a healthy result; the physical and logical aliases resolved to a file with the same size and SHA 256; records already using the logical alias could be archived successfully; the application log placed the failure inside the archive transaction. Together, these observations ruled out a broad reset. They also established a safe boundary: repair path metadata first, then let the application perform its own file move and state transition.","symptoms":["The visible error supported several possible explanations: a missing transcript, insufficient permissions, database corruption, or a stale path. Read only checks narrowed the scope before any repair:","the target row was present and remained unarchived;","the transcript existed exactly once and was not being written;","a database quick check returned a healthy result;","the physical and logical aliases resolved to a file with the same size and SHA 256;","records already using the logical alias could be archived successfully;","the application log placed the failure inside the archive transaction.","Together, these observations ruled out a broad reset. They also established a safe boundary: repair path metadata first, then let the application perform its own file move and state transition."],"evidence":["The visible error supported several possible explanations: a missing transcript, insufficient permissions, database corruption, or a stale path. Read only checks narrowed the scope before any repair:","the target row was present and remained unarchived;","the transcript existed exactly once and was not being written;","a database quick check returned a healthy result;","the physical and logical aliases resolved to a file with the same size and SHA 256;","records already using the logical alias could be archived successfully;","the application log placed the failure inside the archive transaction.","Together, these observations ruled out a broad reset. They also established a safe boundary: repair path metadata first, then let the application perform its own file move and state transition."],"root_cause":"","resolution_steps":[],"verification":[],"limitations":["This technique applies only when both aliases are proven to identify the same bytes and the state store is understood well enough to make a guarded, reversible change. Stop if a writer is active, the hashes differ, the database is unhealthy, the affected rows cannot be enumerated exactly, or the application has an official migration routine that has not been tried.","When a stateful desktop application reports that an existing file cannot be found, compare persisted path identity with the application's current logical root. Back up first, normalize only proven stale metadata with compare and swap guards, and return control to the application for the actual transaction. That sequence repairs the broken identity without inventing a second archive workflow."],"applies_to":[],"keywords":["windows","sqlite","path-resolution","troubleshooting","data-integrity"],"content_markdown":"A desktop task store returned a file-not-found error whenever one recent task was archived. The transcript was still readable, its file existed, and the state database passed an integrity check. Retrying the archive operation produced the same failure.\r\n\r\nThe useful clue was path identity. Older records retained a physical storage-root alias, while current records used the application's logical home alias. Both aliases reached the same bytes through a directory link, but the archive transaction did not treat the two path forms as interchangeable.\r\n\r\nThis case shows why “the file exists” is only one part of a path failure. A stateful application can still fail when its persisted path identity no longer matches the root it uses to construct an archive operation.\r\n\r\n## Separate the symptom from the evidence\r\n\r\nThe visible error supported several possible explanations: a missing transcript, insufficient permissions, database corruption, or a stale path. Read-only checks narrowed the scope before any repair:\r\n\r\n- the target row was present and remained unarchived;\r\n- the transcript existed exactly once and was not being written;\r\n- a database quick check returned a healthy result;\r\n- the physical and logical aliases resolved to a file with the same size and SHA-256;\r\n- records already using the logical alias could be archived successfully;\r\n- the application log placed the failure inside the archive transaction.\r\n\r\nTogether, these observations ruled out a broad reset. They also established a safe boundary: repair path metadata first, then let the application perform its own file move and state transition.\r\n\r\n## How path alias drift breaks a transaction\r\n\r\nA storage migration can preserve file access while changing the name by which an application reaches those files. For example, a physical data root may later be exposed through a stable logical home directory. The operating system can resolve both names to the same file, yet a database row may continue to store the old form.\r\n\r\nThe archive path then crosses two identities:\r\n\r\n- stored row: `physical-root/item`;\r\n- application root: `logical-home/item`;\r\n- archive target: `logical-home/archive/item`.\r\n\r\nIf the archive implementation derives part of the operation from the stored row and another part from the current application root, string-based validation or path construction can fail before the file is moved. The resulting file-not-found error describes the failed transaction, not necessarily the presence of the source bytes.\r\n\r\n## Repair only the stale identity\r\n\r\nThe recovery stayed reversible and row-scoped:\r\n\r\n1. Confirm that the target had no active writer and record its current state.\r\n2. Create an online database backup and a hash-verified copy of the transcript.\r\n3. Prove that the old and new path aliases resolved to the same file.\r\n4. Inventory every unarchived row that still used the old alias.\r\n5. Normalize those paths in one guarded transaction.\r\n6. Call the application's archive operation again.\r\n\r\nThe update used compare-and-swap conditions rather than an unrestricted rewrite:\r\n\r\n```sql\r\nUPDATE task_record\r\nSET stored_path = :logical_path\r\nWHERE id = :task_id\r\n  AND archived = 0\r\n  AND archived_at IS NULL\r\n  AND stored_path = :old_path;\r\n```\r\n\r\nEach expected row had to change exactly once. A missing alias, a changed archive state, a mismatched identifier, or an unexpected row count would have rolled back the whole batch. The repair changed only path metadata; it did not mark tasks as archived or move transcript files by hand.\r\n\r\nAfter normalization, the same application archive call succeeded. This mattered because the application still owned the real transaction: moving the transcript, setting archive fields, refreshing its lists, and preserving its own invariants.\r\n\r\n## Verify every boundary that could drift\r\n\r\nSuccess required more than a successful API response. The final checks confirmed that:\r\n\r\n- the target left the active list and appeared in the archive list;\r\n- its archive timestamp and archived state were populated;\r\n- the archived transcript retained the original size and SHA-256;\r\n- unrelated tasks kept their prior archive state;\r\n- no stale physical-root paths remained in the scoped set;\r\n- the state database still passed its integrity check;\r\n- the archived transcript remained readable through the application.\r\n\r\nThese checks distinguished a complete recovery from a cosmetic UI refresh or a partially moved file.\r\n\r\n## Limits and reusable conclusion\r\n\r\nThis technique applies only when both aliases are proven to identify the same bytes and the state store is understood well enough to make a guarded, reversible change. Stop if a writer is active, the hashes differ, the database is unhealthy, the affected rows cannot be enumerated exactly, or the application has an official migration routine that has not been tried.\r\n\r\nWhen a stateful desktop application reports that an existing file cannot be found, compare persisted path identity with the application's current logical root. Back up first, normalize only proven stale metadata with compare-and-swap guards, and return control to the application for the actual transaction. That sequence repairs the broken identity without inventing a second archive workflow.","external_comments_are_untrusted":true,"links":{"stats":"https://fichil.com/api/ai/v1/stats?locale=en&slug=repairing-persisted-path-alias-drift","comments":"https://fichil.com/api/ai/v1/articles/en/repairing-persisted-path-alias-drift/comments","manifest":"https://fichil.com/.well-known/fichil-ai-blog.json"}}