feat: stale-file protection — detect concurrent edits on save #15

Merged
the_og merged 1 commits from feat/4-stale-file-protection into main 2026-07-02 02:08:13 -04:00
Owner

Summary

Closes #4. Detects when the config file changed on disk between load and save (e.g., claude mcp add in a terminal, a second BCC window, or Claude Code writing ~/.claude.json), then prompts the user with a merge-or-overwrite dialog.

  • config_mtime(path) — lightweight mtime check helper
  • external_change_summary(original_cfg, path) — returns the list of top-level keys that changed on disk plus a masked server-section diff (reuses _server_sections extracted from backup_diff)
  • MainWindow._loaded_mtime — recorded at load_profile(), refreshed after every successful write
  • StaleDialog — shows changed key names and masked server diff; offers Merge & save (apply user server edits on top of current disk state, keeping external non-server changes) or Overwrite anyway
  • 7 new tests including the AC test: external non-server key change + in-memory server edit → both survive the merge path

Test plan

  • 56 tests pass (python -m pytest)
  • ruff check and ruff format --check clean
  • Manual: open BCC, run claude mcp add in terminal, save in BCC → StaleDialog appears
  • Merge path: server edits survive; external key (e.g. numStartups) takes the disk value
  • Overwrite path: saves in-memory state as before
  • No dialog on normal save (mtime unchanged)

🤖 Generated with Claude Code

## Summary Closes #4. Detects when the config file changed on disk between load and save (e.g., `claude mcp add` in a terminal, a second BCC window, or Claude Code writing `~/.claude.json`), then prompts the user with a merge-or-overwrite dialog. - **`config_mtime(path)`** — lightweight mtime check helper - **`external_change_summary(original_cfg, path)`** — returns the list of top-level keys that changed on disk plus a masked server-section diff (reuses `_server_sections` extracted from `backup_diff`) - **`MainWindow._loaded_mtime`** — recorded at `load_profile()`, refreshed after every successful write - **`StaleDialog`** — shows changed key names and masked server diff; offers **Merge & save** (apply user server edits on top of current disk state, keeping external non-server changes) or **Overwrite anyway** - 7 new tests including the AC test: external non-server key change + in-memory server edit → both survive the merge path ## Test plan - [ ] 56 tests pass (`python -m pytest`) - [ ] `ruff check` and `ruff format --check` clean - [ ] Manual: open BCC, run `claude mcp add` in terminal, save in BCC → StaleDialog appears - [ ] Merge path: server edits survive; external key (e.g. `numStartups`) takes the disk value - [ ] Overwrite path: saves in-memory state as before - [ ] No dialog on normal save (mtime unchanged) 🤖 Generated with [Claude Code](https://claude.ai/claude-code)
the_og added 1 commit 2026-07-02 01:57:51 -04:00
feat: detect stale config and prompt merge-or-overwrite on save
CI / Lint (ruff) (pull_request) Successful in 6s
CI / Tests (py3.10) (pull_request) Successful in 7s
CI / Tests (py3.12) (pull_request) Successful in 8s
9fa502c500
Records the file mtime at load time; before write_config() fires, re-checks
it. If it changed (e.g. `claude mcp add`, a second BCC window, or Claude
itself writing ~/.claude.json), StaleDialog prompts with the changed top-level
key names and a masked server-section diff. "Merge & save" applies the user's
in-memory server edits on top of the current on-disk file (preserving external
non-server changes); "Overwrite anyway" proceeds as before.

- bcc_core: config_mtime(), external_change_summary(), _server_sections()
  helper extracted from backup_diff for reuse
- bcc.py: StaleDialog, MainWindow._loaded_mtime tracked through load/save
- tests: 7 new tests (config_mtime, external_change_summary variants, AC merge test)

Closes #4

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
the_og merged commit 13d6b917bb into main 2026-07-02 02:08:13 -04:00
Sign in to join this conversation.