Skip to content

search_code: invalid regex under regex: true silently returns empty instead of erroring #283

@cryptomaltese

Description

@cryptomaltese

Summary

When search_code is called with regex: true and a syntactically invalid regex (e.g. unclosed group (), the tool returns {"results": [], "total_grep_matches": 0, "raw_match_count": 0} — the same shape as a legitimate no-match. No error, no warning, no diagnostic field.

A caller has no way to distinguish "your pattern is broken" from "your pattern is fine but nothing matches." The former is a bug to fix; the latter is an answer to accept. Conflating them leads to mis-diagnosis and wasted time.

Reproduction

Indexed repo: any Python project with make_snapshot and RateSnapshot identifiers.

# Invalid regex — unclosed '('
search_code(
    project="<project>",
    pattern="make_snapshot|RateSnapshot(",
    file_pattern="tests/dnpm_v2/test_scanner_core.py",
    mode="compact",
    limit=5,
    regex=True,
)
# → {"results": [], "total_grep_matches": 0, "raw_match_count": 0}

Remove the trailing ( and it works:

search_code(pattern="make_snapshot|RateSnapshot", regex=True, ...)
# → 17 results, 39 grep matches

The backing regex engine (ripgrep/PCRE/RE2 — whichever is used) refuses to compile make_snapshot|RateSnapshot( because of the unclosed group. The MCP either catches the exception and returns empty, or invokes the engine in a way that doesn't surface parse errors.

Expected behavior

One of:

  1. Return an explicit error field, e.g. {"error": "invalid regex: unclosed group near position 23", "results": []} — same top-level shape, extra field that callers can check.
  2. Raise an MCP tool error (visible to the client as a failed call) rather than a success response with no matches.
  3. Fall back to literal matching when the regex fails to compile, with a warning field ({"warning": "regex compile failed, falling back to literal", "results": [...]}).

The worst outcome is the current one — silent success with no matches.

Why this matters

  • Combined with search_code: default regex=false treats | as literal pipe, not alternation (silent 0-match trap) #282 (default regex: false treats | as literal), this creates a double-layer trap where a grep-muscle-memory user gets 0 results and has no signal about which issue is in play.
  • The zero-match response eats time in large sessions because debugging "why does search_code not find this thing I can see with rg" takes many probe calls to narrow down.
  • A single error: or warning: field in the response would collapse most of that debug effort.

Suggested implementation

Wherever the regex is compiled inside search_code:

try:
    compiled = re.compile(pattern) if regex else re.escape(pattern)
except re.error as exc:
    return {
        "results": [],
        "raw_matches": [],
        "total_grep_matches": 0,
        "total_results": 0,
        "error": f"invalid regex: {exc.msg} at position {exc.pos}",
    }

Adapt to whatever engine is actually in use (Go regexp.Compile, ripgrep exit code, etc.) — the important part is that a compile failure becomes a distinguishable response.

Environment

  • MCP package: codebase-memory-mcp (version as surfaced via tool list in Claude Code session, 2026-04-22)
  • Client: Claude Code
  • Linux; issue is in error-handling of the regex compile path, not platform-specific

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