From 94cc22be3b5264fe0d429fc408ecdfa457c3254e Mon Sep 17 00:00:00 2001 From: ayushcodes10 Date: Tue, 22 Sep 2026 13:57:26 +0530 Subject: [PATCH 1/3] Degrade instead of aborting when cargo introspection finds no manifest FileNotFoundError is a subclass of OSError, so a missing Cargo.toml at the scan root was caught by the same handler meant for ImportError and connection failures and exited the whole process. That fires after the AST pass has already completed, discarding every file of completed structural extraction work just because an optional enrichment source found nothing to read, an ordinary condition for any repo whose manifest lives in a subdirectory, such as a Tauri app that keeps it under its own separate frontend adjacent crate folder. cargo_result already starts as the empty, handled shape the merge step downstream expects, so catch FileNotFoundError on its own, print a note, and let extraction continue with an empty cargo result instead of exiting. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh --- graphify/cli.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/graphify/cli.py b/graphify/cli.py index 969d7d3c87..e2bbb03ed7 100644 --- a/graphify/cli.py +++ b/graphify/cli.py @@ -4182,6 +4182,14 @@ def _progress(idx: int, total: int, _result: dict) -> None: print("[graphify extract] introspecting Cargo workspace...") try: cargo_result = introspect_cargo(target) + except FileNotFoundError: + # No Cargo.toml at the scan root is an ordinary condition + # (e.g. Tauri keeps its manifest under src-tauri/), not a + # failure — the AST pass already completed and cargo_result + # is already the empty, handled shape the merge below + # expects, so degrade instead of discarding that work (#3677). + print("[graphify extract] --cargo: no Cargo.toml at scan root, " + "skipping crate edges") except (ConnectionError, ImportError, OSError) as exc: print(f"error: {exc}", file=sys.stderr) sys.exit(1) From a9b14fef06c082f1044574a0fe560b59af06655f Mon Sep 17 00:00:00 2001 From: ayushcodes10 Date: Tue, 22 Sep 2026 13:57:33 +0530 Subject: [PATCH 2/3] Add regression tests for cargo extraction with a missing manifest One test runs extract with the cargo flag against a repo that has no Cargo.toml at all and confirms it still succeeds and still writes the graph built from the code already indexed, with a note explaining the crate edges were skipped. A companion test confirms a repo that does have a valid manifest still gets its crate node, so the fix only changes the missing manifest path. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh --- tests/test_cargo_missing_manifest.py | 71 ++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 tests/test_cargo_missing_manifest.py diff --git a/tests/test_cargo_missing_manifest.py b/tests/test_cargo_missing_manifest.py new file mode 100644 index 0000000000..af900b6afb --- /dev/null +++ b/tests/test_cargo_missing_manifest.py @@ -0,0 +1,71 @@ +"""`graphify extract --cargo` must degrade, not abort, when no Cargo.toml +exists at the scan root (#3677). + +A missing root manifest is an ordinary condition (e.g. a Tauri app keeps its +manifest under src-tauri/), not a failure. Before the fix, FileNotFoundError +(a subclass of OSError) was caught by a handler meant for ImportError/ +ConnectionError and the whole process exited, discarding the AST pass that +had already completed and never writing graph.json. +""" +from __future__ import annotations + +import json +import os +import subprocess +import sys +from pathlib import Path + +PYTHON = sys.executable +_KEY_VARS = ("GEMINI_API_KEY", "GOOGLE_API_KEY", "OPENAI_API_KEY", "OPENAI_BASE_URL", + "ANTHROPIC_API_KEY", "MOONSHOT_API_KEY", "DEEPSEEK_API_KEY") + + +def _run(repo: Path, *extra: str): + env = {k: v for k, v in os.environ.items() if k not in _KEY_VARS} + env["GRAPHIFY_OUT"] = str(repo / "graphify-out") + return subprocess.run( + [PYTHON, "-m", "graphify", "extract", ".", "--code-only", "--cargo", *extra], + cwd=repo, capture_output=True, text=True, env=env, + ) + + +def test_cargo_flag_without_a_manifest_still_writes_the_graph(tmp_path): + repo = tmp_path / "repo" + repo.mkdir() + (repo / "app.py").write_text("def hello():\n return 1\n", encoding="utf-8") + + r = _run(repo) + + assert r.returncode == 0, ( + f"a missing Cargo.toml must not abort the whole extraction: {r.stderr}" + ) + out = r.stdout + r.stderr + assert "no Cargo.toml at scan root" in out, ( + f"the missing manifest should be reported as a skip, not silently dropped: {out}" + ) + graph = repo / "graphify-out" / "graph.json" + assert graph.exists(), "the AST work already done must still be written to graph.json" + g = json.loads(graph.read_text(encoding="utf-8")) + labels = [n.get("label") for n in g["nodes"]] + assert any(str(l).startswith("hello") for l in labels), "code was indexed" + + +def test_cargo_flag_with_a_manifest_still_adds_crate_nodes(tmp_path): + repo = tmp_path / "repo" + repo.mkdir() + (repo / "app.py").write_text("def hello():\n return 1\n", encoding="utf-8") + (repo / "Cargo.toml").write_text( + '[package]\nname = "app"\nversion = "0.1.0"\nedition = "2021"\n', + encoding="utf-8", + ) + + r = _run(repo) + + assert r.returncode == 0, f"a valid manifest must extract cleanly: {r.stderr}" + graph = repo / "graphify-out" / "graph.json" + assert graph.exists() + g = json.loads(graph.read_text(encoding="utf-8")) + node_ids = {n.get("id") for n in g["nodes"]} + assert any("app" in str(i) for i in node_ids), ( + f"a real Cargo.toml should still contribute a crate node; got {sorted(node_ids)}" + ) From 30a587f8669bed5c37c4f7eea9fd716d082b5398 Mon Sep 17 00:00:00 2001 From: ayushcodes10 Date: Tue, 22 Sep 2026 13:57:48 +0530 Subject: [PATCH 3/3] Add changelog entry for issue 3677 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c3e8767dc..bc2ae8386f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu - Fix: the incremental rebuild no longer purges AST nodes it just reported as fail-closed "kept" — the eviction pass re-checks the kept set, so a moved-file/symlink layout can't deadlock the shrink guard into refusing every update (#3697, #3695, thanks @hopstreax). - Fix: `graph.html` no longer crashes vis-network with a stack overflow on large graphs — nodes are seeded on a spiral before physics runs so overlap-avoidance can't blow the layout recursion (#3699, thanks @sanjaiyan-dev). - Fix: node and edge tooltips now show special characters literally (C++ templates like `vector`, generics, `&`, quotes) instead of raw HTML entities, while the HTML sinks that need escaping keep it (#3686, #3664, thanks @hopstreax). +- Fix: `graphify extract --cargo` no longer aborts the whole extraction, discarding the AST pass that already completed, when no `Cargo.toml` exists at the scan root — an ordinary condition for any repo whose manifest lives in a subdirectory. It now prints a note and continues with an empty cargo result instead, matching the merge step's existing handling of that shape (#3677, thanks @ExhibitJ). - Docs: repository links now point at `Graphify-Labs/graphify` instead of the old account (including in generated wiki output), translated READMEs use the current logo, GitHub issue/PR templates were added, and the Enterprise link was corrected (#3692, #3694, #3693, thanks @Abdul535). ## 0.9.64 (2026-09-18)