From 524289fb663f12ed5abb8923d9a933481acfda60 Mon Sep 17 00:00:00 2001 From: nucleusjay Date: Tue, 9 Jun 2026 03:36:19 -0400 Subject: [PATCH] Fix two latent bugs: merge-chunks output and manifest data loss 1. graphify merge-chunks dumped the entire node list to the terminal instead of the node count. __main__.py concatenated merged['nodes'] (a list of dicts) into an f-string where it clearly meant len(merged['nodes']) -- the other two values in the same line use len() correctly. 2. global_graph._load_manifest silently returned a fresh empty manifest on any JSON parse error. That is reachable through normal interrupted writes (the manifest is rewritten in full on every global_add / global_remove, with no fsync or atomic rename), and the failure mode is total data loss: every tracked repo disappears from ~/.graphify/global-manifest.json on the next read. Back the corrupt file up to .corrupt. and print to stderr before returning the empty default. Users can then recover manually and the failure is visible rather than silent. --- graphify/__main__.py | 2 +- graphify/global_graph.py | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/graphify/__main__.py b/graphify/__main__.py index 759d913b..ce6f15e9 100644 --- a/graphify/__main__.py +++ b/graphify/__main__.py @@ -4520,7 +4520,7 @@ def main() -> None: out_path.parent.mkdir(parents=True, exist_ok=True) out_path.write_text(json.dumps(merged, ensure_ascii=False), encoding="utf-8") print( - f"Merged {len(chunk_files)} chunks: {merged['nodes']} nodes, {len(merged['edges'])} edges, " + f"Merged {len(chunk_files)} chunks: {len(merged['nodes'])} nodes, {len(merged['edges'])} edges, " f"{merged['input_tokens']:,} in / {merged['output_tokens']:,} out tokens" ) diff --git a/graphify/global_graph.py b/graphify/global_graph.py index c6310f94..24e2af27 100644 --- a/graphify/global_graph.py +++ b/graphify/global_graph.py @@ -16,8 +16,27 @@ def _load_manifest() -> dict: if _GLOBAL_MANIFEST.exists(): try: return json.loads(_GLOBAL_MANIFEST.read_text(encoding="utf-8")) - except Exception: - pass + except Exception as exc: + # Don't silently wipe the user's manifest on a parse error: that + # deletes every tracked repo. Back the bad file up and surface the + # error so the user can recover or report it. + backup = _GLOBAL_MANIFEST.with_suffix( + _GLOBAL_MANIFEST.suffix + f".corrupt.{int(datetime.now(timezone.utc).timestamp())}" + ) + try: + _GLOBAL_MANIFEST.rename(backup) + print( + f"[graphify global] manifest at {_GLOBAL_MANIFEST} failed to parse ({exc}); " + f"moved to {backup} and starting fresh. Restore from the backup if this was " + f"unexpected.", + file=sys.stderr, + ) + except Exception as rename_exc: + print( + f"[graphify global] manifest at {_GLOBAL_MANIFEST} failed to parse ({exc}) " + f"and could not be backed up ({rename_exc}). Starting fresh.", + file=sys.stderr, + ) return {"version": 1, "repos": {}}