fix(core): invalidate config cache when file is modified by another process#662
fix(core): invalidate config cache when file is modified by another process#662
Conversation
|
Claude finished @groksrc's task in 2m 41s —— View job PR Review: Invalidate config cache when file is modified by another processTasks:
SummaryThis PR correctly addresses a real-world staleness problem for long-lived processes (MCP stdio server). The approach is sound and implementation is clean. A few observations worth noting below. Code Quality
The mtime-based cache validation is a well-chosen, low-overhead solution. A few notes:
Tests
Fixture updates — All conftest files that previously cleared One observation on test isolation: Several tests in Team Checklist
VerdictApprove. The implementation is correct, clean, and well-tested. The single notable edge case (TOCTOU race) is acceptable and standard for mtime-based caches. The |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 089d7b1bb6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
src/basic_memory/config.py
Outdated
| if current_mtime is not None and current_mtime == _CONFIG_MTIME: | ||
| return _CONFIG_CACHE |
There was a problem hiding this comment.
Use stronger change detection than second-level mtime
The cache hit check relies on current_mtime == _CONFIG_MTIME, so on filesystems with coarse timestamp granularity (the tests already acknowledge 1s granularity) a cross-process config write that happens within the same timestamp tick will not invalidate the cache and the process will keep serving stale config. This means long-lived MCP processes can still miss project set-cloud/set-local updates in the exact workflow this patch is trying to fix.
Useful? React with 👍 / 👎.
089d7b1 to
49897a6
Compare
|
Claude finished @groksrc's task in 2m 27s —— View job PR Review: Invalidate config cache when file is modified by another processTasks:
SummaryThe approach is correct and well-suited for the problem. An mtime-based cache guard is a low-overhead, platform-compatible solution for cross-process config invalidation. The implementation is clean and the comment style follows the Trigger/Why/Outcome convention from CLAUDE.md. One potential test regression was found. Code Quality
The core logic at lines 681–692 is correct: if _CONFIG_CACHE is not None:
try:
current_mtime = self.config_file.stat().st_mtime
except OSError:
current_mtime = None
if current_mtime is not None and current_mtime == _CONFIG_MTIME:
return _CONFIG_CACHE
# mtime changed or file gone — invalidate and fall through to re-read
_CONFIG_CACHE = None
_CONFIG_MTIME = None
Minor nit: TOCTOU window (lines 683 vs 749): The file mtime is read twice — once to check the cache and once to record after re-reading. A concurrent write between these two calls would store stale content with a fresh mtime. This is standard and self-correcting on the next external write. Acceptable for this use case. Codex's coarse-granularity concern (P2): Filesystems with 1-second mtime resolution (FAT32, some network mounts) could miss a write that lands within the same second. The test already handles this with Potential Test Regression
# line 1259 - injects a modified config directly
config_module._CONFIG_CACHE = app_config # _CONFIG_MTIME is still NoneWith the new code, any subsequent
The test injects This test passes today only if Recommended fix: Update config_module._CONFIG_CACHE = app_config
config_module._CONFIG_MTIME = float("inf") # prevent mtime invalidationOr better, use Tests
Team Checklist
VerdictImplementation is correct and the approach is well-chosen. The one item worth addressing before merge is the potential test regression in |
Add mtime-based cache validation so long-lived processes (like the MCP stdio server) detect when the config file has been modified externally (e.g. by `bm project set-cloud` in a separate terminal). This is a cheap os.stat() call per config access that only triggers a re-read when the file mtime has actually changed. Fixes #660 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Drew Cain <groksrc@gmail.com>
Addresses Codex review feedback: on filesystems with coarse mtime granularity (1s resolution), two writes within the same second would share the same mtime. Adding file size to the check catches config changes even when mtime doesn't change, since config edits almost always change file size. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Drew Cain <groksrc@gmail.com>
49897a6 to
b3fc169
Compare
|
Claude finished @groksrc's task in 3m 34s —— View job PR Review: Invalidate config cache when file is modified by another processTasks:
SummaryThis PR adds mtime+size-based cache invalidation to Confirmed Test Regression
The overwrite if overwrite is not None else ConfigManager().config.write_note_overwrite_default
This is a confirmed regression that will cause # Line 1259 and 1283 — add _CONFIG_MTIME and _CONFIG_SIZE alongside _CONFIG_CACHE
config_module._CONFIG_CACHE = app_config
config_module._CONFIG_MTIME = float("inf") # prevents mtime invalidation
config_module._CONFIG_SIZE = 0 # prevents size invalidationCode Quality
The mtime+size dual-check is well-chosen — guarding against coarse-granularity filesystems where two writes within the same second share the same mtime. The Trigger/Why/Outcome comment style is followed correctly: # Outcome: cheap os.stat() per access; re-read only when mtime or size differs.A few minor notes:
Tests
Team Checklist
VerdictRequest changes. One confirmed regression: |
…ests
The mtime+size cache guard introduced in this branch would discard
injected _CONFIG_CACHE values because float("inf") never matches any
real file mtime. Use the actual config file's mtime and size so the
guard sees a match and keeps the injected config.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
|
Claude finished @groksrc's task in 1m 42s —— View job PR Review: Invalidate config cache when file is modified by another processTasks:
SummaryThis PR correctly addresses the config staleness problem for long-lived processes (MCP stdio server). The three commits tell a clean story: initial mtime-only fix → upgrade to mtime+size for coarse-granularity filesystem safety → fix the cache injection pattern in write_note tests. All previously flagged issues have been resolved. This is ready to merge. Code Quality:
|
| Category | Item | Status |
|---|---|---|
| Code Quality | Follows Basic Memory coding conventions (literate style, Trigger/Why/Outcome) | ✅ |
| Python 3.12+ type annotations | ✅ | |
| SQLAlchemy 2.0 / FastAPI / Typer conventions | N/A | |
| 100-character line length | ✅ | |
| No commented-out code | ✅ | |
| Testing | Unit tests for new functions | ✅ |
| Integration tests for MCP tools | N/A | |
| Edge cases covered (file gone, mtime+size change, save reset) | ✅ | |
| 100% test coverage maintained | ✅ | |
test_tool_write_note.py cache injection now sets all three globals |
✅ (fixed in 6e7cfc7) | |
| Architecture | Preserves local-first principles | ✅ |
| No DB schema changes (no migrations needed) | ✅ | |
| Security | No hardcoded secrets | ✅ |
| Proper error handling (OSError caught) | ✅ | |
| No sensitive data in logs | ✅ | |
| Compatibility | Windows-compatible os.stat().st_mtime and st_size |
✅ |
| No emoji/unicode in console or log output | ✅ |
Verdict
Approve. All previously raised concerns are resolved. The implementation is correct, the regression is fixed, and all tests properly track the full cache state. The only remaining item is the _STALE_KEYS nit (module-level constant vs. local variable) which is low priority and can be addressed as a follow-up.
Summary
ConfigManager.load_config()so long-lived processes (MCP stdio server) detect external config changes_CONFIG_MTIMEalongside_CONFIG_CACHE— cheapos.stat()per access, re-read only when mtime differssave_config()_CONFIG_CACHEto also clear_CONFIG_MTIMEPreviously, running
bm project set-cloudin a terminal had no effect on the MCP server because the config was cached at the module level and never re-read from disk.Test plan
Fixes #660
🤖 Generated with Claude Code