Repository navigation
cfg-gated twin functions collapse into one node; get_code_snippet returns the inactive branch's body #495
Description
Bug Description
When two items share a qualified name and are distinguished only by #[cfg(...)] attributes (the common Rust #[cfg(feature = "x")] / #[cfg(not(feature = "x"))] pair), the indexer stores a single graph node. get_code_snippet then returns the body of one arbitrary branch — in practice the inactive/fallback one — silently hiding the real implementation. An agent asking "what does f do?" gets a misleading answer (e.g. "it's a no-op returning None") when the feature-enabled build has a full implementation.
Steps to Reproduce
Index any Rust crate containing a cfg-gated twin, e.g.:
#[cfg(feature = "rag-pdf")]
fn try_extract_pdf_text(bytes: &[u8]) -> Option<String> {
if bytes.len() < 5 || &bytes[..5] != b"%PDF-" { return None; }
/* real extraction */
}
#[cfg(not(feature = "rag-pdf"))]
fn try_extract_pdf_text(_bytes: &[u8]) -> Option<String> { None }query_graph: MATCH (n) WHERE n.name = 'try_extract_pdf_text'
RETURN n.qualified_name, n.start_line, n.end_line, n.signature
Observed
total: 1— only one node exists for the two definitions.- It is the
#[cfg(not(...))]stub:start_line 385,end_line 387,signature (_bytes: &[u8]). get_code_snippetreturnsfn try_extract_pdf_text(_bytes: &[u8]) -> Option<String> { None }— the real#[cfg(feature = "rag-pdf")]impl (lines 372–383) is absent from the graph entirely.
Expected
Either (a) keep both definitions as distinct nodes tagged by their cfg predicate, or (b) at minimum surface that the symbol has multiple cfg-gated definitions so get_code_snippet can't silently return a non-representative branch. Returning the inactive fallback as if it were the implementation is the worst outcome.
Impact
get_code_snippet— a core tool — can return source that is misleading or the opposite of the real behavior.- Complexity/metrics (
complexity,cognitive,bt) are computed from whichever branch wins, so they misrepresent the active build. - Common in real Rust:
#[cfg(feature)],#[cfg(test)],#[cfg(unix)]/#[cfg(windows)]twins.
Relation to existing issues
Distinct from #405 (which targets call-edge resolution via the rust-analyzer integration) — this is a node-extraction / snippet-fidelity defect that's independent of call-graph accuracy and fixable in the existing C pipeline. Not #478 (cross-package bare-name collision) nor #438 (edge→Module attribution).
Environment
- codebase-memory-mcp
v0.8.1 - Project: Rust workspace (~786 Rust files), Windows 11
- Repro symbol:
crates/zeroclaw-runtime/src/tools/file_read.rs
Activity
DeusData commentedon Jun 18, 2026
Excellent report, thank you @metalmon 🙌 And thanks for cleanly distinguishing it from #405, #478, and #438. You're right that this is a node extraction and snippet fidelity defect, fixable in the C pipeline independent of call graph accuracy.
The worst part is exactly what you flagged:
get_code_snippetsilently returning the inactivecfg(not(...))fallback as if it were the implementation. Our direction is to keep both definitions as distinct nodes keyed by theircfgpredicate (which also fixes the complexity and metrics misattribution you noted), possibly with an interim marker noting that a symbol has multiplecfggated definitions. Common pattern, so worth doing right. Thanks again!test(repro): -Itests fix + reproductions for #495, #521, #382
DeusData commentedon Jun 28, 2026
This issue now has a first-class, durable reproduction in the cumulative bug-reproduction suite landing in #667 — a test that pins the exact failing behaviour, so it's tracked and guarded against silent regression. Where #667 also lands the root-cause fix, that's called out in the PR; otherwise the reproduction makes the eventual fix straightforward and verifiable.
The reproduction goes live once #667 merges. (Posted from the release branch's QA pass.)
fix: 17 reproduce-first issue + new-bug fixes (per-case verified)
fix(fqn): keep the extension in __file__ QNs so sibling files stay di…
fix(pipeline): attribute cfg-gated Rust calls to their function, not …
docs(issues): close the fixed items, correct the false claim, re-file…
fix(extract): give each Go init() its own QN
fix(extract): give Go build-constrained twin files distinct QNs
fix(extract): give Go build-constrained twin files distinct QNs
fix(rust): fence a QN on the function's own cfg predicate, balanced
fix(rust): fence a QN on the function's own cfg predicate, balanced