Skip to content

[BUG] write_note returns "Note already exists" when called with explicit overwrite=true (regression from #766) #818

@cderv

Description

@cderv

Bug Description

After PR #766 added AliasChoices to MCP tool parameters, write_note returns the
"note already exists" error even when called with an explicit overwrite=true. The
parameter appears to be dropped before reaching the function body. The only call-site
workaround I've found is delete_note followed by write_note without overwrite.

Steps To Reproduce

From an MCP client (Claude Code, in my case):

  1. Create a note:

    write_note(title="overwrite-repro", content="v1", directory="test")
    
  2. Attempt to overwrite the same path:

    write_note(title="overwrite-repro", content="v2", directory="test", overwrite=true)
    

Expected Behavior

The note at test/overwrite-repro is replaced with v2.

Actual Behavior

The call returns the standard "already exists" guidance message, which itself suggests
overwrite=True as the fix:

# Error: Note already exists

**"overwrite-repro"** already exists (permalink: `test/overwrite-repro`).

`write_note` does not overwrite by default. Choose an option:

| Goal | Action |
|------|--------|
| ... |
| Full replace | write_note("overwrite-repro", ..., overwrite=True) |

The note on disk is unchanged. delete_note("test/overwrite-repro") followed by a fresh
write_note(...) (no overwrite argument) succeeds.

Environment

  • OS: Windows 11 Pro 10.0.26200
  • Python: 3.13 (via uv)
  • Basic Memory: 0.20.3
  • Installation: uv tool
  • MCP client: Claude Code

Additional Context

The overwrite parameter on write_note was changed in commit ee1558ea (PR #766,
"accept training-data-friendly parameter aliases") from:

overwrite: bool | None = None,

to:

overwrite: Annotated[
    bool | None,
    Field(default=None, validation_alias=AliasChoices("overwrite", "force", "replace")),
] = None,

The function-body check looks correct on a static read:

effective_overwrite = overwrite if overwrite is not None else ConfigManager().config.write_note_overwrite_default
# ...
if not effective_overwrite:
    # raises the "already exists" path

So True would pass this gate if it arrived. The error path firing means overwrite is
arriving as None (or as the config default False) at the function body — i.e. the
explicit true from the MCP client isn't surviving FastMCP's argument extraction. I
haven't traced FastMCP's handling of Field(validation_alias=...) on a function
parameter (vs a Pydantic model field), so I can't say whether the regression is in
FastMCP, in how this annotation is interpreted, or something else. The directory
parameter received the same AliasChoices treatment in the same commit and works for
me, so if it's an alias-resolution issue it may be specific to bool | None typed
parameters.

Two workarounds that do work:

  • Set BASIC_MEMORY_WRITE_NOTE_OVERWRITE_DEFAULT=true (or
    write_note_overwrite_default: true in config) — flips the None-branch default so
    dropped overwrite values behave as overwrite=true.
  • At the call site, delete_note first, then write_note without an overwrite
    argument.

Possible Solution

I haven't tested a patch. Happy to test on Windows + Claude Code if a hypothesis or fix
is worth verifying.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions