Skip to content

[BUG] search_notes(tags="a,b") ignores comma-separated tags (inconsistent with the tag: query shorthand) #910

@phernandez

Description

@phernandez

Bug

Write a note tagged ['alpha','beta']. search_notes(query="tag:alpha,beta") matches it (the tool's tag: parser splits commas). The same intent via the parameter search_notes(tags="alpha,beta") returns ZERO results because coerce_list wraps the string as the single literal tag ['alpha,beta'], which matches nothing. Within one tool, comma-string tags behave two different ways. It is also inconsistent with write_note, whose documented tags convention splits comma-strings. (CLI search-notes is unaffected — it uses repeatable --tag and builds a real list.)

Root cause

src/basic_memory/mcp/tools/search.py:679-682 applies BeforeValidator(coerce_list) to tags. coerce_list (src/basic_memory/utils.py:559-572) JSON-parses array strings but for any other string returns [v] (wraps without splitting). Meanwhile the tool's own tag: query parser at search.py:882-884 explicitly splits comma-separated lists ('tag:coffee,brewing'), creating the internal inconsistency. The same coerce_list non-split also affects note_types/entity_types/categories comma-strings (search.py:646/653/661).

Suggested fix

Use a tag-aware coercer for the search_notes tags parameter that splits bare comma-strings (mirroring parse_tags / the tag: shorthand), e.g. replace BeforeValidator(coerce_list) on tags with BeforeValidator(parse_tags) (parse_tags already handles list, JSON-array string, and comma-string consistently and returns [] for None — adjust to return None when empty if needed). For note_types/entity_types/categories, decide whether comma-strings are supported; if so, apply the same comma-splitting coercer so all list filters behave consistently.

Repro (failing integration test, no mocks)

This test asserts the correct behavior and fails on current main (FAILS on HEAD: AssertionError: tags='alpha,beta' param must behave like the tag: shorthand (both split commas). query_hit=True param_hit=False. (Companion test test_search_notes_tags_comma_string_matc). It was produced by an automated integration-test bug hunt and can be dropped into test-int/ as the regression test once fixed.

"""Bug hunt: search_notes internal inconsistency — the `tag:` query shorthand
splits comma-separated tags (search.py:882-884), but the `tags=` parameter does
NOT (coerce_list wraps the whole string). Same comma string, two different results
within ONE tool.
"""

import pytest
from fastmcp import Client


@pytest.mark.asyncio
async def test_tags_param_vs_tag_query_comma_consistency(mcp_server, app, test_project):
    async with Client(mcp_server) as client:
        # Note tagged alpha + beta
        await client.call_tool(
            "write_note",
            {
                "project": test_project.name,
                "title": "Tag Shorthand Note",
                "directory": "tag-shorthand",
                "content": "# Tag Shorthand Note\n\nTagShorthandToken body",
                "tags": ["alpha", "beta"],
            },
        )

        # Path A: tag: query shorthand with comma list -> splits, matches
        via_query = await client.call_tool(
            "search_notes",
            {
                "project": test_project.name,
                "query": "tag:alpha,beta",
                "search_type": "text",
            },
        )
        query_hit = "Tag Shorthand Note" in via_query.content[0].text

        # Path B: tags= parameter with the SAME comma string
        via_param = await client.call_tool(
            "search_notes",
            {
                "project": test_project.name,
                "query": "TagShorthandToken",
                "search_type": "text",
                "tags": "alpha,beta",
            },
        )
        param_hit = "Tag Shorthand Note" in via_param.content[0].text

        assert query_hit, "tag: query shorthand should match (sanity)"
        assert param_hit == query_hit, (
            "tags='alpha,beta' param must behave like the tag: shorthand "
            f"(both split commas). query_hit={query_hit} param_hit={param_hit}"
        )

🤖 Found via automated integration-test bug hunt (Claude Code)

Metadata

Metadata

Assignees

Labels

bugSomething isn't working
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions