Codex review of PR #962 identified two real issues:
1. CLI bypass: the BeforeValidator(parse_str_list) on note_types, entity_types, and
categories only fires through MCP/Pydantic validation. The CLI path in
cli/commands/tool.py calls search_notes() directly, so `bm tool search-notes
--type note,task` arrived as note_types=["note,task"] and matched nothing.
Fix: add in-body parse_str_list() normalization for all three params (mirroring
the existing parse_tags() call for tags on the same code path).
2. Silent stringify: parse_str_list used str(raw) in the list branch, so [42] became
["42"] before Pydantic saw it, accepting invalid input as a no-result search
instead of rejecting it. Fix: guard against non-string list elements and return
the original value unchanged so Pydantic rejects it with a clear error.
Tests added: annotation-level split tests for note_types/entity_types/categories,
non-string-element rejection tests, async direct-call regression for note_types,
and unit-level parse_str_list non-string list tests in test_coerce.py.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>
Add `parse_str_list` to utils.py — like `parse_tags` but without stripping
'#' — and wire it as the BeforeValidator for note_types, entity_types, and
categories in search_notes. This makes passing "note,task" or
'["note","task"]' work correctly instead of being wrapped as a single literal
value by coerce_list.
coerce_list is left unchanged; canvas and other callers that depend on its
wrap-single-string behaviour are unaffected.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Drew Cain <groksrc@gmail.com>