Skip to content

reliability(patterns): atomic save of usage cache#14

Open
CryptoJones wants to merge 1 commit into
mainfrom
reliability/patterns-cache-atomic-save
Open

reliability(patterns): atomic save of usage cache#14
CryptoJones wants to merge 1 commit into
mainfrom
reliability/patterns-cache-atomic-save

Conversation

@CryptoJones
Copy link
Copy Markdown
Owner

`socrates patterns review` on a CompanyOS with many projects can take
seconds to walk every .md file. A SIGINT during the final
`(patterns_dir / .usage-cache.json).write_text(...)` previously left a
truncated/invalid file. The next run's `_load_usage_cache` would
JSONDecodeError on the partial file and return None — silently
discarding ALL cached segments and forcing a full re-scan. (The current
load already swallows the JSONDecodeError into None; the bug is the
silent cache reset, not a crash.)

Fix: same atomic-write pattern as interview.py's save —
`.tmp` + os.replace. Cache is either fully old or fully new,
never half. Tempfile cleaned up on both success and failure paths.

Tests added (2):

  • post-save: no .tmp leftover
  • mid-save os.replace failure: pre-existing cache survives intact,
    no .tmp leftover

149/149 tests pass; ruff + mypy clean.

Self-review caveat: duplicates the atomic-write logic in #refactor/shared-atomic-write-and-decide-lock (which extracts it to _atomic.py). Merge order: refactor first → rebase this onto the shared helper. Functionally correct either way.

\`socrates patterns review\` on a CompanyOS with many projects can take
seconds to walk every .md file. A SIGINT during the final
\`(patterns_dir / .usage-cache.json).write_text(...)\` previously left a
truncated/invalid file. The next run's \`_load_usage_cache\` would
JSONDecodeError on the partial file and return None — silently
discarding ALL cached segments and forcing a full re-scan. (The current
load already swallows the JSONDecodeError into None; the bug is the
silent cache reset, not a crash.)

Fix: same atomic-write pattern as interview.py's save —
\`<file>.tmp\` + os.replace. Cache is either fully old or fully new,
never half. Tempfile cleaned up on both success and failure paths.

Tests added (2):
- post-save: no .tmp leftover
- mid-save os.replace failure: pre-existing cache survives intact,
  no .tmp leftover

149/149 tests pass; ruff + mypy clean.
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.

1 participant