Repository navigation
ingest_traces reports pairs_unmatched for endpoints that query_graph` serves; first large batch stores, all subsequent calls unmatched (fresh index/daemon reproduce) #2725
Description
Version
codebase-memory-mcp 0.12.1 (build 77745491f0a9), Linux x86-64, installed 2026-10-20 via the official installer.
Platform
Linux (x64)
Install channel
GitHub release archive / install.sh / install.ps1
Binary variant
standard
What happened, and what did you expect?
Issue: ingest_traces rejects pairs whose endpoints exist in the graph
Title: ingest_traces reports pairs_unmatched for endpoints that query_graph serves; first large batch stores, all subsequent calls unmatched (fresh index/daemon reproduce)
Environment
- codebase-memory-mcp 0.12.1 (build 77745491f0a9), Linux x86-64, installed 2026-10-20 via the official installer.
- Project: legacy monorepo, ~42k nodes / ~152k edges after
.cbmignoreexclusions (Perl + PL/pgSQL + Go + Template Toolkit). - Client: MCP session from Kimi Code CLI.
Summary
ingest_traces validates endpoints with SELECT 1 FROM nodes WHERE project=? AND qualified_name=? (per src/mcp/mcp.c). On a fresh index, single-trace calls with endpoints that query_graph returns are rejected as unmatched, and pairs_stored stays 0. The first large batch (~250 traces) stored 249 pairs; every call after that — including re-sending previously stored pairs and pairs that are real CALLS edges returned by query_graph — is rejected. pair_total freezes at the value reached after the second batch.
Additional observations
- Reproduces after:
daemon stop/client restart;delete_project+ full reindex from scratch (fresh DB, ~37 s build); single-trace minimal payload. So it is not stale state or a corrupted store. - Reproduces on a minimal project (two .py files, 3 nodes, 1 CALLS edge): ingesting exactly the pair returned by
MATCH (a)-[r:CALLS]->(b)givespairs_unmatched: 1. Not specific to the original project. - The node data is present in the project
.dbfile (verified withstringsonnodesrows: project, label, qualified_name, file_path). - Schema (
src/store/store.c): singlenodestable,UNIQUE(project, qualified_name)— so the exists-check query shape looks correct. - Related quirk:
query_graphwithWHERE n.qualified_name = '<qn>'returns 0 rows, whileWHERE n.qualified_name IN ['<qn>']finds the node, andWHERE f.file_path = ... AND f.name = ...finds it andRETURNs exactly thatqualified_name. Suggests equality matching onqualified_nameis tokenized/broken in the cypher layer; possibly the same lookup is used by the ingest validation. - One large batch behaved differently: the very first
ingest_tracescall (~250 pairs) stored 249/250; everything after was rejected. Looks like a write-transaction/visibility window rather than per-pair validation logic.
Expected behavior
A trace whose both endpoint qns resolve to nodes in the current generation is stored (pairs_stored increments, visible to trace_path overlay).
Use case behind the ingest
Linking Template Toolkit page templates to the PL/pgSQL API functions they call via a dispatcher (api('info.date') in templates → method registered in a ws.method INSERT → implementing function). Static analysis can't see these edges; runtime/derived traces are the only way. Because the API method itself (info.date) is a data registration, not a code symbol, there is no node for it — see feature request below.
Feature request: user-defined node layer (or ingest with node creation)
For systems where an "API method" is a row in a registry table rather than a declared symbol, it would be valuable to declare a custom layer of nodes (e.g. ApiMethod labeled nodes keyed by method name) and then ingest observed edges Template -> ApiMethod -> Function. Currently ingest_traces must not fabricate nodes (per RUNTIME_TRACE_MODEL.md), and no MCP surface creates nodes — so such edges are impossible without synthetic bridge files in the indexed tree. A declare_nodes/upsert_nodes tool (scoped to a label, replaceable per re-generation) would cover this class of use cases cleanly.
Reproduction
Minimal reproduction
-
Index a project (full mode). Confirm a node exists:
query_graph:MATCH (f:Function) WHERE f.file_path = 'pwl/sql/ws/52_i18n_dep.sql' AND f.name = 'date_info' RETURN f.qualified_name
→home-app_test-tpro2.pwl.sql.ws.52_i18n_dep.date_info -
Ingest a trace with exactly that qn:
ingest_traces( project = "home-app_test-tpro2", label = "repro", traces = [{"caller": "home-app_test_tpro2.pwl.tmpl.page.app.administrate_messages", "callee": "home-app_test_tpro2.pwl.sql.ws.52_i18n_dep.date_info", "count": 1}] ) -
Response:
{"status":"stored","pairs_stored":0,"pairs_unmatched":1, "unmatched_sample":["home-app_test_tpro2.pwl.tmpl.page.app.administrate_messages", "home-app_test-tpro2.pwl.sql.ws.52_i18n_dep.date_info"], "pair_total":0}
Both endpoints are served by query_graph (the Module node exists: MATCH (m:Module) WHERE m.file_path = '...administrate_messages.tt2' RETURN count(m) → 1). Reproduces with a pair taken verbatim from a MATCH (a)-[r:CALLS]->(b) query as well.
Logs
Diagnostics trajectory (memory / performance / leak issues)
Project scale (if relevant)
No response
Confirmations
- I searched existing issues and this is not a duplicate.
- My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.
Activity