From e005931cb5a2a47a1052e685ace1f24725db4363 Mon Sep 17 00:00:00 2001 From: Nirav Patel Date: Sat, 22 Aug 2026 14:47:34 -0400 Subject: [PATCH] codex review fixes: duplicate-post recovery, fsync, fatal corrupt state, narrower comment drop - create_post: on HTTPError, look for an existing post tagged ledger: (insert-then-error recovery) and adopt it before retrying - save_state: fsync tmp file before rename - load_state: corrupt state is fatal (auto-reseed would duplicate every open item's post) - comment: drop only on 500/404/410; 401/403/429/502/503 retry next poll Accepted as-is (with rationale): same-second ordering relies on the feed replaying the JSONL in append order + Python's stable sort; seed-window add+close races reduce to the documented 'predates bridge' skip. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01YECpkAwQUwgu7NVy91R8fW --- bridge.py | 53 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/bridge.py b/bridge.py index 1fe9cf4..e01dcbd 100644 --- a/bridge.py +++ b/bridge.py @@ -97,7 +97,11 @@ def load_state() -> dict: try: return json.loads(STATE_FILE.read_text(encoding="utf-8")) except json.JSONDecodeError: - log(f"WARN state file corrupt, starting fresh: {STATE_FILE}") + # Starting fresh here would re-seed every open item -> a duplicate + # post per item. Refuse to run until a human inspects the file. + log(f"FATAL state file corrupt: {STATE_FILE} — fix or remove it " + "deliberately; refusing to reseed automatically") + sys.exit(1) return {"checkpoint": "", "seen": [], "map": {}, "seeded": False, "channel_id": ""} @@ -105,7 +109,10 @@ def load_state() -> dict: def save_state(state: dict) -> None: STATE_FILE.parent.mkdir(parents=True, exist_ok=True) tmp = STATE_FILE.with_suffix(".json.tmp") - tmp.write_text(json.dumps(state, indent=1), encoding="utf-8") + with open(tmp, "w", encoding="utf-8") as f: + json.dump(state, f, indent=1) + f.flush() + os.fsync(f.fileno()) os.replace(tmp, STATE_FILE) @@ -239,10 +246,36 @@ def send_message(channel_id: str, content: str, thread_id=None) -> dict: body) +def find_existing_post(state: dict, ledger_id: str): + """Recovery lookup: the target API can insert a row and then fail, so a + retried create must first check whether a post tagged ledger: already + exists. Bounded to the most recent 200 channel messages.""" + try: + msgs = api("GET", f"/api/workspaces/{WORKSPACE_ID}/comms/channels/" + f"{ensure_channel(state)}/messages?limit=200") or [] + except Exception: + return None + needle = f"ledger:{ledger_id}" + for m in msgs: + if not m.get("thread_id") and needle in (m.get("content") or ""): + return m.get("id") + return None + + def create_post(state: dict, item: dict) -> None: if item["id"] in state["map"]: return # already mirrored (seed/feed overlap) - msg = send_message(ensure_channel(state), post_body(item)) + try: + msg = send_message(ensure_channel(state), post_body(item)) + except urllib.error.HTTPError: + # The insert may have landed before the error (proven insert-then-500 + # behaviour). Adopt an existing post instead of duplicating on retry. + existing = find_existing_post(state, item["id"]) + if existing: + state["map"][item["id"]] = existing + log(f"post {item['id'][:8]} -> adopted existing {existing}") + return + raise # genuinely not created; retry next poll state["map"][item["id"]] = msg["id"] log(f"post {item['id'][:8]} -> {msg['id']} ({item.get('title', '')[:60]})") @@ -255,11 +288,15 @@ def comment(state: dict, ledger_id: str, content: str, what: str) -> None: try: msg = send_message(ensure_channel(state), content, thread_id=post_id) except urllib.error.HTTPError as e: - # mib007 comms can 500 AFTER the insert (proven: mention-path crash), - # so retrying a failed comment risks duplicates. Comments are - # best-effort: log and move on instead of blocking the checkpoint. - log(f"DROP {what} {ledger_id[:8]}: HTTP {e.code} (comment not retried)") - return + # 500: mib007 comms can fail AFTER the insert (proven mention-path + # crash) — retrying risks duplicates, so drop. 404/410: the post is + # gone — retrying can never succeed. Everything else (401/403/429/ + # 502/503) is safe to retry next poll. + if e.code in (500, 404, 410): + log(f"DROP {what} {ledger_id[:8]}: HTTP {e.code} " + "(comment not retried)") + return + raise log(f"{what:6} {ledger_id[:8]} -> comment {msg['id']}")