fix(mcp): tighten search_notes tags input and normalize for direct callers#941
fix(mcp): tighten search_notes tags input and normalize for direct callers#941phernandez wants to merge 5 commits into
Conversation
…llers Follow-up to #932, addressing two P2 Codex review findings that merged unresolved: 1. Reject malformed tag values at the tool boundary. The bare parse_tags BeforeValidator stringified unsupported types (tags=42 became ["42"], dicts became junk tags), silently turning caller mistakes into no-result searches. New strict_search_tags wrapper in utils.py only normalizes str/list/None and passes other types through unchanged so Pydantic rejects them with a clear validation error. parse_tags itself is unchanged for its frontmatter and write_note callers. 2. Normalize tags for direct callers too. The comma splitting lived only in the BeforeValidator, which runs only through the MCP validation layer. cli/commands/tool.py calls search_notes directly with Typer's collected list, so `bm tool search-notes --tag a,b` forwarded ["a,b"] as one literal tag and matched nothing. The function body now runs parse_tags where the other filter params are normalized, so every path gets the same comma-split semantics. Refs #910 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3a62bac4db
ℹ️ 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".
A JSON array with malformed entries like tags=[42] or tags=[{"a": 1}]
was treated as a valid list and parse_tags stringified each element
([42] -> ["42"]) before Pydantic validated List[str], silently turning
caller type mistakes into no-result searches.
strict_search_tags now only normalizes lists whose elements are all
strings; non-conforming lists pass through unchanged so Pydantic rejects
them with a clear validation error. str and None behavior is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 72350e5bd4
ℹ️ 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".
strict_search_tags guarded native lists with non-string elements but still
delegated JSON-array strings directly to parse_tags, whose recursive JSON
handling stringified bad elements ('[42]' -> ["42"]) before Pydantic could
validate List[str] — preserving the silent no-result behavior for MCP
clients that serialize arrays as strings.
Mirror parse_tags' JSON-array detection at the boundary: if the parsed list
contains any non-string element, pass the original string through unchanged
so Pydantic rejects it with a clear validation error. Valid all-string JSON
arrays and plain comma strings keep working as before. parse_tags itself is
unchanged (frontmatter callers rely on its lenient behavior).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
|
Addressed the BM Bossbot blocking finding on the JSON-array-string tags path in f0290fe.
Coverage: unit tests in |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Follow-up to #932, which switched the
search_notestagsparameter fromBeforeValidator(coerce_list)toBeforeValidator(parse_tags)but merged with two unresolved P2 Codex review findings. Refs #910, follow-up to #932.Finding 1 — Reject malformed tag values before parsing
parse_tagsstringifies unsupported types:tags=42became["42"],tags={"a": 1}became junk tags. The oldcoerce_listpath let Pydantic reject non-list/str values, so #932 weakened the tool boundary — caller type mistakes turned into silent no-result searches.Fix: new
strict_search_tagswrapper inutils.pyused as theBeforeValidator. It only normalizesstr/list/None(delegating toparse_tags) and passes any other type through unchanged so Pydantic fails validation with a clear error.parse_tagsitself is untouched, so its frontmatter /write_notecallers keep their existing behavior.Finding 2 — Normalize tags for direct tool callers too
The comma splitting lived only in the
BeforeValidator, which runs only through the MCP validation layer.src/basic_memory/cli/commands/tool.pyimportssearch_notesand calls it directly with Typer's collected list, sobm tool search-notes --tag alpha,betaforwarded["alpha,beta"]as one literal tag and silently matched nothing.Fix: the
search_notesfunction body now runsparse_tags(tags)alongside the other filter-param normalization (before thetag:shorthand merge andSearchQueryconstruction), so direct callers get the same comma-split/list semantics. TheBeforeValidatoris retained for the MCP boundary strictness of finding 1;parse_tagsis idempotent, so MCP-validated input passes through unchanged.Test plan
All new tests were verified to fail on unfixed code (src changes stashed) before the fix was applied.
tests/test_coerce.py::TestStrictSearchTags— unit coverage of the new coercer: None/comma-string/list/JSON-array normalization, plus int/dict passthrough for Pydantic rejectiontests/mcp/test_tool_search.py::test_search_notes_tags_annotation_rejects_non_string_types—tags=42andtags={"a": 1}raiseValidationErrorthrough the annotation'sTypeAdaptertests/mcp/test_tool_search.py::test_search_notes_tags_invalid_type_rejected_via_mcp— same inputs through a real fastmcpClientraiseToolErrortests/mcp/test_tool_search.py::test_search_notes_direct_call_splits_comma_tags— directsearch_notes(tags=["alpha,beta"])call (the CLI path) matches notes taggedalpha, with a negative controltags="a,b",["a","b"],'["a","b"]',"a",Noneuv run pytest tests/mcp/test_tool_search.py tests/test_coerce.py tests/utils/ -x -q→ 239 passedruff format/ruff check/ty checkclean on all touched files🤖 Generated with Claude Code
Reviewed SHA:
cb4a4ad7c64a19ad84cf648e323dcce8d6cfc90cVerdict:
approveStatus:
success- BM Bossbot approved this head SHASummary:
Reviewed the full provided diff for the latest head SHA. The prior JSON-array string validation gap is covered by the updated strict_search_tags logic and regression tests, and I did not find remaining merge-blocking correctness, boundary, or compatibility issues. I could not run the project tests locally because the environment lacks installed Python dependencies, so this approval is based on static review of the supplied diff.
Blocking findings:
Non-blocking findings:
BM Bossbot image choices
#941/home/runner/work/basic-memory/basic-memory/docs/assets/infographics/pr-941.webpgpt-image-21536x1024higheditorial-imageautoTheme / visual direction: