Skip to content

query_graph: grouping by a function expression silently returns one wrong row #1292

Open

Description

@melihemreguler

Summary

When the grouping key in RETURN is a function call rather than a property, the executor does not group. It returns a single row and writes the aggregate value into the non-aggregate column. No error is raised.

Repro

query_graph --query "MATCH (n) RETURN labels(n) AS label, count(*) AS c ORDER BY c DESC"

Actual:

{"columns":["label","c"],"rows":[["21143","21143"]]}

Expected: one row per label — ["Function", 14960], ["Variable", 1747], ["Macro", 1066], … (these counts are correct and are what get_graph_schema reports).

What does work

  • labels(n) as a plain projection: MATCH (n:Function) RETURN labels(n) LIMIT 1 -> ["Function"]
  • Property-based grouping: MATCH (n) RETURN n.file_path, count(*) -> correct per-file counts

Both halves work in isolation; only the combination fails.

Why this matters

The failure is silent. The caller receives a well-formed result with a plausible-looking number in it, so an LLM agent consuming this reports the wrong answer confidently. The engine already rejects unsupported constructs loudly and correctly (CREATE -> unsupported Cypher feature: CREATE clause); an unsupported ... error here would be strictly better than a wrong row.


Environment

  • Version: codebase-memory-mcp 0.9.0 (release binary, darwin-arm64)
  • OS: macOS 26.5.2, Apple Silicon
  • Repo under test: DeusData/codebase-memory-mcp @ af3ffbd (21,143 nodes / 123,103 edges)
  • Verified identically through both the MCP server and cli mode (outputs byte-identical)

Activity

  1. added
    cypherCypher query language parser/executor bugs
    parsing/qualityGraph extraction bugs, false positives, missing edges
    on Jul 27, 2026
  2. added
    bugSomething isn't working
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    on Jul 28, 2026
  3. added this to the 0.9.1-rc milestone on Jul 28, 2026
  4. DeusData commented on Jul 28, 2026

    @DeusData
    Owner

    Thanks for the scalar-function grouping reproduction.

    Current main distinguishes aggregate functions with is_aggregate_func (src/cypher/cypher.c:3621), but the grouping key is built through the separate projection path at src/cypher/cypher.c:3719-3748. That split is consistent with a function expression being classified/projected incorrectly and collapsing rows into one group.

    PR #1279 appears to target this exact execution path and is queued for review; it is not pre-approved by triage. The issue is routed to 0.9.1-rc at priority/high because the result is silently wrong. Thank you for making the expected groups explicit.

  5. artaommahe commented on Jul 30, 2026

    @artaommahe

    Confirming this on 0.9.1-rc.1 (macOS arm64, standard variant, GitHub release archive), and adding three findings from a duplicate I filed and have since closed (#1358).

    1. The trigger is broader than "the grouping key is a function call". Every function-call column in an aggregating RETURN is dropped from the group key and overwritten with the aggregate value — even when grouping succeeds correctly on a different, property-based column in the same query:

    MATCH (a)-[r]->(b) RETURN type(r) AS t, a.name AS n, count(*) AS c LIMIT 10
      "2" AliasConsumer "2"        <- grouped correctly by a.name; t is clobbered with the count
      "10" README.md "10"
      "5" alias-consumer.ts "5"
    

    So it isn't "grouping fails when the key is a function" so much as "function columns are excluded from the key and then written over".

    2. Two smaller repros showing the function value evaluates to empty inside an aggregate, which may be the more direct handle on it:

    MATCH (a)-[r]->(b) RETURN count(DISTINCT type(r))   ->  1      (expected: number of distinct edge types)
    MATCH (a)-[r]->(b) RETURN collect(type(r))          ->  ["","","", …]
    

    DISTINCT type(r) without an aggregate is fine and returns the correct distinct set, so the defect is confined to the aggregate path.

    3. A workaround worth documenting for users — materialise the function value through WITH before aggregating. Verified correct on both a 58-edge toy graph and a 120,805-edge TypeScript monorepo (23 correct rows there):

    MATCH (a)-[r]->(b) WITH type(r) AS t RETURN t, count(*) AS c ORDER BY c DESC
      DEFINES 31 / CONTAINS_FILE 7 / CALLS 7 / DEFINES_METHOD 6 / USAGE 3 / CONTAINS_FOLDER 2 / IMPORTS 1 / HAS_BRANCH 1
    

    Variants I checked that make no difference to the bug: count(r) vs count(*), presence or absence of ORDER BY, presence or absence of the alias, and labels(n) vs type(r) — all eight combinations fail identically.

    For what it's worth on severity: this is the natural first query to write against an unfamiliar graph ("what edge types exist and how many of each"), and README.md's query-language section lists labels/type as supported functions and count as a supported aggregate, then promises that anything outside the supported subset "fails with a clear unsupported … error rather than returning empty results". A silently wrong row is the one outcome that contract rules out.

    Small repro repo if useful: https://github.com/artaommahe/codebase-memory-mcp-rc-repros (Case E).


    Posted by Claude Code on behalf of @artaommahe

  6. DeusData commented on Sep 19, 2026

    @DeusData
    Owner

    Thank you for separating scalar grouping from functions inside aggregates and supplying the WITH workaround. Current grouping code has changed to classify actual aggregates rather than every function column, but that does not prove count(DISTINCT type(r)) or collect(type(r)) correct. Those examples must remain separate acceptance checks; I cannot claim the whole issue fixed from this inspection.

  7. DeusData commented on Sep 26, 2026

    @DeusData
    Owner

    Thank you, @melihemreguler, for the grouping reproduction, and @artaommahe for the extra cases and the WITH workaround!

    This is fixed as of v0.10.0 (b43bddd). On v0.11.0:

    • MATCH (n) RETURN labels(n) AS label, count(*) AS c returns one row per label.
    • RETURN type(r) AS t, a.name AS n, count(*) groups on both columns without overwriting t.
    • count(DISTINCT type(r)) and collect(type(r)) are now rejected with a clear error instead of returning misleading values. WITH type(r) AS t RETURN t, count(*) remains the supported form.

    Could you confirm on v0.11.0? We'll close this once you do.

  8. added
    awaiting-reporterWaiting on the reporter for info/repro; stale bot will warn then close
    on Sep 26, 2026
  9. github-actions commented on Sep 26, 2026

    @github-actions

    Thanks for the report! To move this forward we need a bit more so we can reproduce it ourselves:

    • the codebase-memory-mcp --version you're on
    • the exact steps or command you ran
    • a public repo (or a small dummy snippet) that shows the problem — please don't paste proprietary code

    Once that's here we'll pick it straight back up. Heads-up: issues left awaiting-reporter are automatically closed after a few weeks of silence, but a comment reopens the door anytime.

  10. DeusData commented on Oct 19, 2026

    @DeusData
    Owner

    Thank you again for the scalar-grouping and aggregate-expression examples. The latest update distinguishes corrected grouping from unsupported function expressions inside aggregates and asks for v0.11.0 confirmation. I have no new result from your environment to add.

  11. DeusData commented on Oct 25, 2026

    @DeusData
    Owner

    Thank you again for the grouping and aggregate-expression cases. Could you confirm the corrected grouping and explicit unsupported-expression behavior on the requested version?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    awaiting-reporterWaiting on the reporter for info/repro; stale bot will warn then closebugSomething isn't workingcypherCypher query language parser/executor bugsparsing/qualityGraph extraction bugs, false positives, missing edgespriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions