Skip to content

Keep one bad snapshot file from costing an object its history - #81

Merged
matt-edmondson merged 1 commit into
mainfrom
claude/project-thread-dwlrfc
Oct 7, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
claude/project-thread-dwlrfc

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Requested by Matt · project thread

Before: one truncated or unparseable file in an object's snapshot folder made SnapshotStore.History throw for that object, and Add of the same element set saw the name was taken and skipped it, so the history stayed unreadable until someone deleted the file by hand without being told which one.

After: the bad file is skipped and named, the rest of the history is returned, and adding the same element set again replaces it. Snapshots are written to a temporary file and moved into place, so an interrupted write can no longer leave a partial file under a real <epoch>.json name.

How: WriteAtomically writes <path>.<guid>.tmp beside the snapshot and File.Moves it over the final name; the temporary name doesn't end in .json, so a stray one is invisible to readers. A new ReadHistory(id) returns a SnapshotHistory of the readable sets plus the unreadable paths; History(id) delegates to it. A parse failure is JsonException (malformed, truncated, or a record the parser rejects) or FormatException (an epoch format it doesn't read); I/O faults still propagate. Add counts a repaired file as new.

Tests are in the new SnapshotStoreTests.cs and cover all three acceptance criteria. Mutation-checked: letting the parse failure escape fails 4 of the 6, and restoring the bare File.Exists check in Add fails the repair test. The atomic write itself isn't mutation-checkable without a seam (it needs a process to die between two syscalls); the tests cover a stray temp file being ignored and no write leaving one behind. dotnet build -c Release and dotnet test -c Release pass locally (110 tests).

Fixes #62

🤖 Generated with Claude Code

https://claude.ai/code/session_0169AU8ddEya4XDCm1FVDCMZ


Generated by Claude Code

SnapshotStore wrote snapshots in place and skipped any name already
taken, so an interrupted write left a truncated file that Add never
repaired, and History threw for the whole object on it. A record a
stricter parser now rejects did the same with no crash at all.

Snapshots are now written to a temporary file and moved into place.
ReadHistory skips any file that does not parse and names it, History
returns the readable sets, and Add replaces an unreadable file instead
of treating it as already archived.

Fixes #62

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0169AU8ddEya4XDCm1FVDCMZ
@matt-edmondson matt-edmondson self-assigned this Oct 7, 2026
@matt-edmondson
matt-edmondson merged commit 08dca86 into main Oct 7, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

One truncated or unparseable snapshot file makes SnapshotStore.History throw for that object permanently, and Add never repairs it

2 participants