feat(scanner): Recover Cargo topology on scan failure - #117
Conversation
When ast-grep fails or times out, recover the Rust dependency graph from `cargo metadata` instead of failing hard. The fallback outcome carries honest provenance (ast-grep `timeout|failed` plus a `rust-cargo` fallback source) so coverage and fail-closed behavior stay consistent with the scan contract. Conditional dependencies are identified and reported as conditional edges.
`cargo metadata` entries with null target fields no longer crash or produce malformed edges.
…ls on a pre-cancelled context Preserve the primary ast-grep error for real scan failures; honour caller cancellation when ScanFiles fails because the context was already cancelled. Co-Authored-By: Whale integration <whale@local>
scanForGraphOutcome only delegated to scanForGraphOutcomeWithFilters with an
empty Filters{} and had no callers; staticcheck U1000.f01d812 to
7d14431CompareJordanCoin
commented
Aug 9, 2026
Heads-up before these land: #117 and #118 don't compile together, in either merge order. Both are green in CI because each is built against Git auto-merges them with no textual conflict — the collision is semantic:
funcdiscoverCargoManifests(rootstring, files []FileInfo) []string#118 changes the signature: funcdiscoverCargoManifests(ctx context.Context, rootstring, files []FileInfo) ([]string, error)#117 adds a new caller ( manifests:=discoverCargoManifests(root, files)Result whichever way round they go: Verified by merging both into a scratch worktree in each order and building. The fix is one line in manifests, err:=discoverCargoManifests(ctx, root, files)
iferr!=nil {
returnScanOutcome{}, err
}Simplest is to fix it in whichever of the two you'd like to merge second. No need to change anything else — #115, #116 and #119 are unaffected, and all five merge cleanly apart from this. |
If you want to merge them both, can you just merge this one first? That was the order for the integration stack of my next wave, I'll fix the other one then 😅 Or you can just update yourself, edits are enabled as always. |
JordanCoin
left a comment
There was a problem hiding this comment.
Reviewed the full Cargo fallback path and provenance handling at 7d14431. The fallback is bounded, preserves the primary failure when unavailable, reports partial evidence honestly, and the full scanner suite passes. This should land before #118, which must then update its changed discoverCargoManifests caller. No blocking finding in this PR — approved.
What does this PR do?
When ast-grep fails or times out, the scanner recovers the Rust dependency graph from
cargo metadatainstead of failing hard:buildFileGraphWithFallbackruns the primary scan, and on anast-grepIncompleteScanErrorbuilds a fallbackScanOutcomefromcargo metadata(buildCargoFallbackOutcome).Sources: [{name: "ast-grep", status: timeout|failed}, {name: "rust-cargo", status: fallback}]) so coverage and fail-closed behavior stay consistent with the scan contract.cargoDependencyIsConditional) and reported as conditional edges.Review follow-ups included
fix(scanner): Normalize null Cargo targets—cargo metadataentries with null target fields no longer crash or produce malformed edges.fix(scanner): return context.Canceled when the fallback file scan fails on a pre-cancelled context— a pre-cancelled caller receivescontext.Canceled(the primary error is still preserved for real scan failures).CLI / MCP surface
No new CLI commands or arguments, and no new MCP tools. Behavior of existing surface:
codemap --deps <path>on a Rust repository returns the recovered graph withrust-cargofallback provenance instead of a hard error when ast-grep is unavailable or times out.get_dependenciesreturns the same fallback outcome with its provenance.Coverage provenance notes
The fallback only engages on an
ast-grepfailure; a successful scan remains authoritative. Degraded outcomes are bounded and deterministic, matching the fail-closed contract.Developed with carefully directed, manually reviewed AI assistance.