From 9f34768497f167ca9df844427f4e13ca486f4b31 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Thu, 12 Nov 2026 18:09:16 +0000 Subject: [PATCH] fix(kvb-check-gate): skip parent journal rows whose text cannot be encoded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A row with a lone surrogate in gate-parents.jsonl, which sandboxed agents can write, raised UnicodeEncodeError at the TEMP table insert and crashed every commit-msg, pre-push and pre-merge-commit hook and the hub gate view. opened() now keeps only rows whose repo, branch and parent encode; the test fails without the change. 🎯 Fix: 97 % 💥 Break: 2 % 💸 Cost: 100 % ⏱️ Time: 100 % 🛡️ Security: 100 % 🏆 Score: 95 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 --- skills/kvb-check-gate/scripts/gates.py | 12 +++++++++++- tests/test_gates.py | 6 ++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/skills/kvb-check-gate/scripts/gates.py b/skills/kvb-check-gate/scripts/gates.py index 45bb5ea..c8d4041 100755 --- a/skills/kvb-check-gate/scripts/gates.py +++ b/skills/kvb-check-gate/scripts/gates.py @@ -184,6 +184,16 @@ def journal(origin: Path, kept_dir: Path, kept: Path) -> list[dict]: return list(merged.values()) +def encodable(value: object) -> bool: + if not isinstance(value, str): + return False + try: + value.encode() + except UnicodeEncodeError: + return False + return True + + def journal_lock(path: Path) -> contextlib.AbstractContextManager: return watch.nofollow.locked(watch.ROOT, path.with_suffix(".lock"), FILE_MODE, None) @@ -262,7 +272,7 @@ def opened(path: Path, write: bool, kept_dir: Path) -> sqlite3.Connection: db.executemany("INSERT INTO recorded VALUES (?, ?, ?)", [ (row["repo"], row["branch"], row["parent"]) for row in journal(watch.PARENTS, kept_dir, kept_dir / watch.KEPT_PARENTS.name) - if all(isinstance(row.get(key), str) for key in ("repo", "branch", "parent")) and row["branch"] != row["parent"] + if all(encodable(row.get(key)) for key in ("repo", "branch", "parent")) and row["branch"] != row["parent"] ]) except BaseException: db.close() diff --git a/tests/test_gates.py b/tests/test_gates.py index b0d0d20..7188007 100644 --- a/tests/test_gates.py +++ b/tests/test_gates.py @@ -139,6 +139,12 @@ class TestGates(Scratch): check = self.gates("receipt", "check", "--repo", repo, "--tree", tree, "--level", "test") self.assertEqual((check.returncode, check.stderr), (0, "")) + def test_a_parent_row_with_a_lone_surrogate_is_skipped_and_the_others_still_count(self): + self.council.mkdir(exist_ok=True) + rows = [{"id": "bad", "repo": str(self.repo), "branch": "main", "parent": "\ud800"}, {"id": "good", "repo": str(self.repo), "branch": "main", "parent": "feature"}] + (self.council / "gate-parents.jsonl").write_text("".join(json.dumps(row) + "\n" for row in rows), encoding="utf-8") + self.assertEqual(self.parents("main"), [{"parent": "feature", "source": "recorded"}]) + def test_a_receipt_older_than_30_days_neither_counts_nor_survives_the_next_write(self): repo, tree = str(self.repo), self.out("rev-parse", "HEAD^{tree}") old = {"id": "old", "repo": repo, "tree": tree, "level": "test", "gpu": "false", "at": time.time() - 31 * 86400}