Skip to content

fix(memory): refuse mutations when on-disk file changed since last read (#26045)#30993

Open
bradhallett wants to merge 1 commit into
NousResearch:mainfrom
bradhallett:fix/memory-replace-on-disk-guard-26045
Open

fix(memory): refuse mutations when on-disk file changed since last read (#26045)#30993
bradhallett wants to merge 1 commit into
NousResearch:mainfrom
bradhallett:fix/memory-replace-on-disk-guard-26045

Conversation

@bradhallett

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #26045 (commit 6855d17). The drift guard detects structurally un-roundtrippable content, but jrhouston-trilogy recommended an additional defense: refuse mutations when the on-disk file has changed since last read — the same contract the patch tool already enforces.

This alone would have prevented the original ~8KB data-loss incident.

Changes

  • tools/memory_tool.py: Added SHA-256 content-hash snapshot at lock acquisition. Before save_to_disk(), re-reads the file and refuses if the hash differs. Applied to all three mutation paths (add, replace, remove).
  • tests/tools/test_memory_tool.py: 6 new tests in TestOnDiskChangeGuard

Relationship to existing drift guard

Both defenses are active and independent:

  1. Drift guard fires first — detects structurally un-roundtrippable content (missing delimiters, oversized entries)
  2. On-disk guard fires second — detects any content change between read and write, even if structurally valid

Test plan

pytest tests/tools/test_memory_tool.py -v   # 46 passed (40 existing + 6 new)

Refs #26045

…e last read (NousResearch#26045)

Add a content-hash guard to the replace, add, and remove mutation paths.
Before flushing, the tool re-reads the on-disk file and compares its
SHA-256 hash to the snapshot taken at lock-acquire time. If the file was
modified externally (concurrent session, patch tool edit, shell append)
between those two points, the mutation is refused with a clear error
message directing the caller to re-read and retry.

This is the same contract the patch tool already enforces ('file was
modified since you last read it on disk'). It supplements the existing
drift guard (round-trip mismatch / oversized-entry detection) and would
alone have prevented the original ~8KB data-loss incident.

New tests:
  - test_replace_refuses_when_file_changed_between_read_and_write
  - test_replace_succeeds_when_file_unchanged
  - test_remove_refuses_when_file_changed_between_read_and_write
  - test_add_refuses_when_file_changed_between_read_and_write
  - test_on_disk_guard_does_not_trigger_when_file_is_new
  - test_on_disk_guard_and_drift_guard_are_independent
@alt-glitch alt-glitch added type/bug Something isn't working tool/memory Memory tool and memory providers P2 Medium — degraded but workaround exists labels May 23, 2026

@jsboige jsboige left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed this carefully. The on-disk change guard is well-designed:

What it does: SHA256 snapshot of file content before lock acquisition, then verification before write in add/replace/remove. Refuses mutation if content changed between read and write — prevents data loss from concurrent sessions (#26045).

Strengths:

  • Static methods (_snapshot_on_disk, _check_on_disk_unchanged) — clean separation, testable
  • Guard fires AFTER drift check — correct ordering (drift is structural, on-disk is temporal)
  • 7 tests covering all 3 mutation paths + edge cases (new file, drift vs on-disk independence)
  • Error messages reference issue #26045 and instruct user to re-read + retry

One observation (non-blocking): The pre_hash is taken just before _reload_target() which re-reads from disk. If the file changes between _snapshot_on_disk() and _reload_target(), the drift guard would likely catch it (unparseable content → backup). If it changes between _reload_target() and the check, the on-disk guard catches it. This covers the full window — good.

LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Medium — degraded but workaround exists tool/memory Memory tool and memory providers type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants