From e22a8f743a807e5a03f7f238f0c3fe0c7024a2d8 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sun, 15 Mar 2026 15:28:19 +0000 Subject: [PATCH] fix: 404 retry loop on delete and URL-encode since param MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes for high 4xx alert rate: 1. Catch urllib.error.HTTPError (not just RuntimeError) for 404 on DELETE in pending_ops — was causing infinite retry loop for already-deleted memories 2. try_sync_delete: treat 404 as success instead of enqueuing a retry that will also 404 forever 3. URL-encode the `since` query param to prevent `+` in timezone offset being decoded to a space (the asyncpg-string-timestamp pattern applied to the sync client) --- src/claude_memory/sync.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/claude_memory/sync.py b/src/claude_memory/sync.py index 82409ad..a638619 100644 --- a/src/claude_memory/sync.py +++ b/src/claude_memory/sync.py @@ -8,6 +8,7 @@ import logging import sqlite3 import threading import urllib.error +import urllib.parse import urllib.request from datetime import datetime, timezone from pathlib import Path @@ -161,7 +162,7 @@ class SyncEngine: if server_id: try: self._api_request("DELETE", f"/api/memories/{server_id}") - except RuntimeError as e: + except (RuntimeError, urllib.error.HTTPError) as e: if "404" in str(e): pass # Already deleted on server else: @@ -181,7 +182,7 @@ class SyncEngine: params = "" ts = self.last_sync_ts if ts: - params = f"?since={ts}" + params = f"?since={urllib.parse.quote(ts, safe='')}" result = self._api_request("GET", f"/api/memories/sync{params}") memories = result.get("memories", []) @@ -321,6 +322,11 @@ class SyncEngine: try: self._api_request("DELETE", f"/api/memories/{server_id}") return True + except urllib.error.HTTPError as e: + if e.code == 404: + return True # Already deleted on server — not an error + self.enqueue_delete(server_id) + return False except Exception: self.enqueue_delete(server_id) return False