From 9b937216572e2380e82a519f786edb298aa876ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9E=AC=ED=98=84?= <231584193+kimdzhekhon@users.noreply.github.com> Date: Sun, 19 Jul 2026 09:15:52 +0900 Subject: [PATCH 1/2] fix(serve): reorder except clauses so the corrupted-graph message is reachable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit json.JSONDecodeError subclasses ValueError, so the broader `except (ValueError, FileNotFoundError)` clause always matched first, making the intended "graph.json is corrupted (...). Re-run /graphify to rebuild." recovery hint dead code — users with a truncated graph.json got the bare json.JSONDecodeError message instead, contradicting the behavior SECURITY.md documents for this exact threat. Move the json.JSONDecodeError clause first so it actually catches. Audited the rest of serve.py's exception handlers for the same narrower-after-broader ordering bug; found no other instance. Fixes #2005. --- graphify/serve.py | 6 +++--- tests/test_serve.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/graphify/serve.py b/graphify/serve.py index 0a0e7bfe3..457171e56 100644 --- a/graphify/serve.py +++ b/graphify/serve.py @@ -55,12 +55,12 @@ def _load_graph(graph_path: str) -> nx.Graph: except Exception: G.graph["_learning_overlay"] = {} return G - except (ValueError, FileNotFoundError) as exc: - print(f"error: {exc}", file=sys.stderr) - sys.exit(1) except json.JSONDecodeError as exc: print(f"error: graph.json is corrupted ({exc}). Re-run /graphify to rebuild.", file=sys.stderr) sys.exit(1) + except (ValueError, FileNotFoundError) as exc: + print(f"error: {exc}", file=sys.stderr) + sys.exit(1) def _communities_from_graph(G: nx.Graph) -> dict[int, list[str]]: diff --git a/tests/test_serve.py b/tests/test_serve.py index 302787187..bf0b4f4b1 100644 --- a/tests/test_serve.py +++ b/tests/test_serve.py @@ -599,6 +599,19 @@ def test_load_graph_missing_file(tmp_path): _load_graph(str(graphify_dir / "nonexistent.json")) +def test_load_graph_corrupted_json_prints_recovery_message(tmp_path, capsys): + """json.JSONDecodeError is a ValueError subclass, so its except clause + must be checked before the bare (ValueError, FileNotFoundError) clause, + or the corrupted-graph recovery hint is unreachable (#2005).""" + p = tmp_path / "graph.json" + p.write_text("{not valid json") + with pytest.raises(SystemExit): + _load_graph(str(p)) + err = capsys.readouterr().err + assert "graph.json is corrupted" in err + assert "Re-run /graphify to rebuild" in err + + def test_load_graph_rejects_oversized_file(monkeypatch, tmp_path, capsys): # #F4: oversized graph.json must fail fast (SystemExit) with a clear error. G = _make_graph() From 80998d9cc0d883f3f2704e7d10a26292710b8c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=9E=AC=ED=98=84?= <231584193+kimdzhekhon@users.noreply.github.com> Date: Sun, 19 Jul 2026 22:55:14 +0900 Subject: [PATCH 2/2] test(serve): pin generic ValueError message against clause-order regression Per review on #2017 (thanks @HerenderKumar): add a guard asserting a non-decode ValueError (e.g. a non-.json path) still prints the plain "must be a .json file" message, not the corrupted-graph hint. Locks in the except-clause order from the parent fix so a future refactor can't silently collapse the two branches back together. --- tests/test_serve.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_serve.py b/tests/test_serve.py index bf0b4f4b1..3baa8d5ed 100644 --- a/tests/test_serve.py +++ b/tests/test_serve.py @@ -612,6 +612,19 @@ def test_load_graph_corrupted_json_prints_recovery_message(tmp_path, capsys): assert "Re-run /graphify to rebuild" in err +def test_load_graph_generic_value_error_message_unchanged(tmp_path, capsys): + """A non-decode ValueError (e.g. a non-.json path) must still print the + generic error, not the corrupted-graph hint — pins the except-clause + order from #2005 so a future refactor can't collapse them back.""" + p = tmp_path / "graph.txt" + p.write_text("not a graph") + with pytest.raises(SystemExit): + _load_graph(str(p)) + err = capsys.readouterr().err + assert "must be a .json file" in err + assert "corrupted" not in err + + def test_load_graph_rejects_oversized_file(monkeypatch, tmp_path, capsys): # #F4: oversized graph.json must fail fast (SystemExit) with a clear error. G = _make_graph()