feat: add overwrite guard to write_note tool#632
Conversation
write_note now errors by default when a note already exists, preventing silent data loss from autonomous agents that accidentally overwrite accumulated content. The error message suggests edit_note alternatives. - Add `overwrite` parameter (bool | None) to write_note — explicit True to replace, False/None consults config default - Add `write_note_overwrite_default` config field (default: False) so users can globally restore pre-v0.20 upsert behavior - Guard lives entirely at the MCP tool layer — no API/service changes - JSON output returns structured `NOTE_ALREADY_EXISTS` error on conflict - Update tool annotations: destructiveHint=True, idempotentHint=False - Fix existing tests that relied on implicit update behavior - Add 7 new tests (6 unit + 1 integration) for the overwrite guard Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7c8842e059
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if output_format == "json": | ||
| return { | ||
| "title": title, | ||
| "permalink": entity.permalink, |
There was a problem hiding this comment.
Return stored permalink for overwrite conflicts
When write_note hits a conflict with overwrite disabled, the JSON/text error uses entity.permalink, which is recomputed from the incoming title/directory rather than looked up from the existing note. This is incorrect for notes that already have a custom frontmatter permalink (a supported flow in this repo), so the returned permalink can point to a non-existent identifier and cause the suggested read_note/edit_note follow-up calls to fail in that scenario.
Useful? React with 👍 / 👎.
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
d684b7a to
7c8842e
Compare
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
Three more test files had write_note calls that create a note then update it without passing overwrite=True: - test_obsidian_yaml_formatting.py: test_write_note_update_preserves_yaml_format - test_tool_json_output_modes.py: test_write_note_text_and_json_modes - test_tool_write_note_metadata.py: test_metadata_survives_update Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
Add write_note overwrite guard (#632) to new capabilities, 8 bug fixes (#631, #630, #30, #31, #28, plus schema_validate/Post/frontmatter fixes), and write_note idempotency breaking change to upgrade notes. Bump commit count from 80+ to 90+. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
Summary
Closes #625
write_notenow errors by default when a note already exists, preventing silent data loss from autonomous agents (OpenClaw, Claude Code agents) that accidentally overwrite accumulated contentoverwriteparameter (bool | None) — passTrueto explicitly replace,False/Noneconsults config defaultwrite_note_overwrite_defaultconfig field (default:False) so users can globally restore pre-v0.20 upsert behavior via config orBASIC_MEMORY_WRITE_NOTE_OVERWRITE_DEFAULTenv varedit_notealternatives (append, prepend, replace_section) andread_notefor inspectionNOTE_ALREADY_EXISTSerror on conflictdestructiveHint=True,idempotentHint=FalseTest plan
pytest tests/mcp/test_tool_contracts.py— signature includesoverwrite(1 test)pytest tests/mcp/test_tool_write_note.py— 40 tests pass (5 existing fixed + 6 new guard tests)pytest test-int/mcp/test_write_note_integration.py— 13 tests pass (1 fixed + 1 new integration test)pytest tests/mcp/test_permalink_collision_file_overwrite.py— 4 tests pass (1 fixed)ruff checkandpyrightpass clean on modified files🤖 Generated with Claude Code