Skip to content

Recover from a saved value a semantic type rejects, and retry Get() after a failed load [patch] - #344

Merged
matt-edmondson merged 2 commits into
mainfrom
claude/appdatastorage-315-semantic-rejection
Oct 9, 2026
Merged

matt-edmondson merged 2 commits into
mainfrom
claude/appdatastorage-315-semantic-rejection

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #315

What changed

  • LoadOrCreate handles more failures. It now sends ArgumentException, FormatException and NotSupportedException thrown during deserialization to the existing archive-and-retry path, the same path JsonException already took. A value can be well-formed JSON and still be rejected by a semantic type: a Windows path read on Linux, validation tightened in an app update, or a hand edit. Before, the converter's ArgumentException escaped and the file stayed on disk, so the app crashed on every launch. Now the file is archived as <file>.corrupt.<timestamp>, the archiving from A settings file that fails to deserialize is deleted outright and replaced with defaults, with no backup left to recover from #314 / Archive a settings file that fails to deserialize instead of deleting it [patch] #337, and defaults are loaded.
  • InternalState is now LazyThreadSafetyMode.PublicationOnly. The default mode cached the first exception, so after one failed load every Get(), QueueSave() and SaveIfRequired() rethrew it for the rest of the process. Now the next Get() tries the load again. LoadOrCreate holds the lock, so racing first calls still load one at a time and only one result is published.

Tests

  • TestLoadOrCreateRecoversFromAValueASemanticTypeRejects: a settings file whose AbsoluteDirectoryPath value is not/absolute. A relative path is rejected on every OS, unlike the issue's C:/… fixture, which is valid on Windows. LoadOrCreate must return defaults and archive the original content.
  • TestGetRetriesAfterAFailedLoadInsteadOfRethrowingIt: a type whose constructor throws once. The second Get() must succeed.
  • Both tests fail when the AppData.cs change is reverted.
  • The full suite passes locally: 111 passed, 0 failed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J9ABDLXQgwyGEboxA8DpmQ


Generated by Claude Code

claude added 2 commits October 8, 2026 12:29
…fter a failed load [patch]

A value that is well-formed JSON but fails a semantic type's validation
surfaced as ArgumentException, which LoadOrCreate did not catch, so it
escaped and the file was left to fail again on every launch. Route
ArgumentException, FormatException and NotSupportedException from
deserialization to the same archive-and-retry path as JsonException.

InternalState was a Lazy<T> in ExecutionAndPublication mode, which caches
the exception, so one failed load made every Get() throw for the rest of
the process. It is now PublicationOnly, so the next Get() tries again.

Fixes #315

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J9ABDLXQgwyGEboxA8DpmQ
@sonarqubecloud

sonarqubecloud Bot commented Oct 8, 2026

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 25c7d7e into main Oct 9, 2026
14 checks passed
@matt-edmondson
matt-edmondson deleted the claude/appdatastorage-315-semantic-rejection branch October 9, 2026 08:00
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.

A saved value that a semantic type rejects makes LoadOrCreate throw ArgumentException, and Get() then throws on every call for the rest of the process

2 participants