From 8741bfeef37add6828f3a6597c61a1503c7f8f67 Mon Sep 17 00:00:00 2001 From: Zo Bot Date: Tue, 26 May 2026 19:29:54 +0000 Subject: [PATCH 01/14] =?UTF-8?q?narrow=20exception=20handlers=20in=20Metr?= =?UTF-8?q?icsBroadcaster=20from=20broad=20Exception=20to=20specific=20typ?= =?UTF-8?q?es=20=E2=80=94=20WebSocket.send=5Ftext=20raises=20(RuntimeError?= =?UTF-8?q?,=20ConnectionResetError)=20when=20disconnected,=20get=5Fmetric?= =?UTF-8?q?s=20raises=20(RuntimeError,=20ValueError)=20for=20asyncio/data?= =?UTF-8?q?=20issues;=20catching=20all=20Exception=20masked=20these=20spec?= =?UTF-8?q?ific=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freerelay/dashboard/ws.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freerelay/dashboard/ws.py b/freerelay/dashboard/ws.py index 2b30577..2074d0f 100644 --- a/freerelay/dashboard/ws.py +++ b/freerelay/dashboard/ws.py @@ -47,7 +47,7 @@ async def broadcast(self, data: dict[str, object]) -> None: for client in self._clients: try: await client.send_text(message) - except Exception: + except (RuntimeError, ConnectionResetError): disconnected.append(client) for client in disconnected: @@ -75,7 +75,7 @@ async def start_broadcasting(self, get_metrics: Any) -> None: "data": metrics, } ) - except Exception as e: + except (RuntimeError, ValueError) as e: logger.error("Metrics broadcast error: %s", e) await asyncio.sleep(self.interval) From cd17fe1a77614bf6fa78ff43f2f560beed536407 Mon Sep 17 00:00:00 2001 From: Zo Bot Date: Tue, 26 May 2026 21:29:50 +0000 Subject: [PATCH 02/14] =?UTF-8?q?narrow=20exception=20in=20=5Fpurge=5Faudi?= =?UTF-8?q?t=20from=20Exception=20to=20redis.asyncio.ResponseError=20?= =?UTF-8?q?=E2=80=94=20redis.xrange=20and=20xdel=20raise=20ResponseError?= =?UTF-8?q?=20on=20failures;=20catching=20all=20Exception=20masked=20this?= =?UTF-8?q?=20specific=20redis=20failure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freerelay/shared/tenancy/audit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freerelay/shared/tenancy/audit.py b/freerelay/shared/tenancy/audit.py index 3a247bd..ea32687 100644 --- a/freerelay/shared/tenancy/audit.py +++ b/freerelay/shared/tenancy/audit.py @@ -221,7 +221,7 @@ async def purge_expired(self, namespace: str) -> int: namespace, ) return len(ids) - except Exception as exc: + except redis.asyncio.ResponseError as exc: logger.error("Failed to purge audit records: %s", exc) return 0 From 01869fa4df564d5647f1d7ffdc4ccf507215a7ff Mon Sep 17 00:00:00 2001 From: Zo Bot Date: Thu, 28 May 2026 09:32:37 +0000 Subject: [PATCH 03/14] =?UTF-8?q?narrow=20exception=20handlers=20in=20shar?= =?UTF-8?q?ed=20security=20crypto=20from=20Exception=20to=20specific=20typ?= =?UTF-8?q?es=20=E2=80=94=20base64.b64decode=20raises=20(ValueError,=20Typ?= =?UTF-8?q?eError)=20for=20invalid=20base64=20input,=20AESGCM.decrypt=20ra?= =?UTF-8?q?ises=20ValueError=20when=20key=20is=20wrong=20or=20tag=20verifi?= =?UTF-8?q?cation=20fails;=20catching=20all=20Exception=20masked=20these?= =?UTF-8?q?=20specific=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freerelay/shared/security/crypto.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freerelay/shared/security/crypto.py b/freerelay/shared/security/crypto.py index b225d8c..fab034c 100644 --- a/freerelay/shared/security/crypto.py +++ b/freerelay/shared/security/crypto.py @@ -68,7 +68,7 @@ def decrypt_key(ciphertext: str, secret: bytes) -> str: try: bundle = base64.b64decode(ciphertext) - except Exception as exc: + except (ValueError, TypeError) as exc: raise ValueError("Invalid base64 ciphertext") from exc if len(bundle) < 28: # 12 (nonce) + 16 (tag) minimum @@ -118,7 +118,7 @@ def _aes_gcm_decrypt(ciphertext: bytes, key: bytes, nonce: bytes, tag: bytes) -> "cryptography package required for AES-256-GCM. " "Install with: pip install cryptography" ) from e - except Exception as exc: + except ValueError as exc: raise ValueError("Decryption failed: invalid key or corrupted data") from exc From 59294e2191a93167afe9a47d5e2f9105037e77b4 Mon Sep 17 00:00:00 2001 From: Zo Bot Date: Thu, 28 May 2026 23:28:12 +0000 Subject: [PATCH 04/14] =?UTF-8?q?narrow=20Redis=20xadd=20exception=20handl?= =?UTF-8?q?er=20in=20=5Fpersist=20from=20Exception=20to=20ResponseError=20?= =?UTF-8?q?=E2=80=94=20redis.xadd=20raises=20ResponseError=20for=20stream?= =?UTF-8?q?=20errors,=20and=20falling=20back=20to=20in-memory=20on=20that?= =?UTF-8?q?=20specific=20failure=20is=20more=20targeted=20than=20masking?= =?UTF-8?q?=20all=20exceptions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freerelay/shared/tenancy/audit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freerelay/shared/tenancy/audit.py b/freerelay/shared/tenancy/audit.py index ea32687..c32f633 100644 --- a/freerelay/shared/tenancy/audit.py +++ b/freerelay/shared/tenancy/audit.py @@ -252,7 +252,7 @@ async def _persist(self, namespace: str, record: AuditRecord) -> None: maxlen=100_000, # Cap stream size ) return - except Exception as exc: + except redis.asyncio.ResponseError as exc: logger.error("Redis audit write failed, falling back to memory: %s", exc) # In-memory fallback @@ -289,7 +289,7 @@ async def _get_from_redis( # Return newest first records.reverse() return records - except Exception as exc: + except redis.asyncio.ResponseError as exc: logger.error("Redis audit read failed: %s", exc) return [] From 1544c7563c021a1907f52cb301ac81c37b919f4b Mon Sep 17 00:00:00 2001 From: Zo Bot Date: Thu, 28 May 2026 23:28:51 +0000 Subject: [PATCH 05/14] =?UTF-8?q?narrow=20exception=20handlers=20in=20auth?= =?UTF-8?q?=20and=20idempotency=20middleware=20=E2=80=94=20get=5Fuser=5Fin?= =?UTF-8?q?fo=20catches=20(OSError,=20KeyError,=20TypeError,=20IndexError)?= =?UTF-8?q?=20from=20dict/getattr=20access=20on=20the=20Supabase=20respons?= =?UTF-8?q?e,=20and=20the=20idempotency=20cache=20handler=20catches=20(Val?= =?UTF-8?q?ueError,=20TypeError)=20from=20json.loads;=20catching=20all=20E?= =?UTF-8?q?xception=20masked=20these=20specific=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freerelay/middleware/auth.py | 2 +- freerelay/middleware/idempotency.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freerelay/middleware/auth.py b/freerelay/middleware/auth.py index 319d0e8..1539fff 100644 --- a/freerelay/middleware/auth.py +++ b/freerelay/middleware/auth.py @@ -67,7 +67,7 @@ def _verify_token_supabase(token_hash: str) -> dict[str, str] | None: _AUTH_CACHE[token_hash] = (user_info, now + _CACHE_TTL) return user_info return None - except Exception as e: + except (OSError, KeyError, TypeError, IndexError) as e: logger.error(f"Supabase auth error: {e}") return None diff --git a/freerelay/middleware/idempotency.py b/freerelay/middleware/idempotency.py index 40b21fc..4149604 100644 --- a/freerelay/middleware/idempotency.py +++ b/freerelay/middleware/idempotency.py @@ -102,7 +102,7 @@ async def dispatch( if len(self._cache) >= _MAX_CACHE_SIZE: self._cache.popitem(last=False) self._cache[idempotency_key] = (time.time(), body, response.status_code) - except Exception: + except (ValueError, TypeError): pass return response From a2d45b97fca71c1eda959bda2eb3cde4010e88d6 Mon Sep 17 00:00:00 2001 From: Zo Bot Date: Thu, 28 May 2026 23:30:11 +0000 Subject: [PATCH 06/14] =?UTF-8?q?narrow=20exception=20handlers=20in=20outc?= =?UTF-8?q?ome=20consumer=20from=20Exception=20to=20specific=20types=20?= =?UTF-8?q?=E2=80=94=20xreadgroup/xpending/xclaim=20raise=20ResponseError,?= =?UTF-8?q?=20OutcomeRecord.from=5Fstream=20raises=20(ValueError,=20TypeEr?= =?UTF-8?q?ror),=20xack=20operates=20on=20Redis=20int=20response=20?= =?UTF-8?q?=E2=80=94=20catching=20all=20Exception=20masked=20these=20speci?= =?UTF-8?q?fic=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freerelay/control_plane/learner/outcome_consumer.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/freerelay/control_plane/learner/outcome_consumer.py b/freerelay/control_plane/learner/outcome_consumer.py index d5f1448..da983b6 100644 --- a/freerelay/control_plane/learner/outcome_consumer.py +++ b/freerelay/control_plane/learner/outcome_consumer.py @@ -136,7 +136,7 @@ async def ensure_group(self) -> None: logger.info( "consumer_group_created stream=%s group=%s", self._stream, self._group ) - except Exception as exc: + except redis.asyncio.ResponseError as exc: # BUSYGROUP means the group already exists — that's fine if "BUSYGROUP" in str(exc): logger.debug( @@ -167,7 +167,7 @@ async def consume_batch( count=batch_size, block=block_ms, ) - except Exception: + except redis.asyncio.ResponseError: logger.exception("consume_batch_read_error") return [] @@ -177,7 +177,7 @@ async def consume_batch( try: record = OutcomeRecord.from_stream(msg_id, fields) records.append(record) - except Exception: + except (json.JSONDecodeError, ValueError, TypeError): logger.exception("consume_batch_parse_error id=%s", msg_id) if records: @@ -195,7 +195,7 @@ async def acknowledge(self, message_ids: list[str]) -> int: count = await self._redis.xack(self._stream, self._group, *message_ids) logger.debug("acked_outcomes count=%d", count) return count - except Exception: + except (json.JSONDecodeError, ValueError, TypeError): logger.exception("acknowledge_error") return 0 @@ -224,7 +224,7 @@ async def pending_info(self) -> dict[str, Any]: "max_id": info.get("max"), "consumers": info.get("consumers", []), } - except Exception: + except redis.asyncio.ResponseError: logger.exception("pending_info_error") return {"pending_count": 0} @@ -274,6 +274,6 @@ async def claim_stale( logger.info("claimed_stale_outcomes count=%d", len(records)) return records - except Exception: + except redis.asyncio.ResponseError: logger.exception("claim_stale_error") return [] From 7f0ab30c8c1f37a82d9a6ec946e1024bb02cc0e3 Mon Sep 17 00:00:00 2001 From: Zo Bot Date: Thu, 28 May 2026 23:30:17 +0000 Subject: [PATCH 07/14] =?UTF-8?q?fix=20acknowledge=20exception=20handler?= =?UTF-8?q?=20to=20catch=20ResponseError=20instead=20of=20JSON=20decode=20?= =?UTF-8?q?errors=20=E2=80=94=20xack=20returns=20an=20integer=20count,=20s?= =?UTF-8?q?o=20only=20Redis-level=20failures=20apply?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freerelay/control_plane/learner/outcome_consumer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freerelay/control_plane/learner/outcome_consumer.py b/freerelay/control_plane/learner/outcome_consumer.py index da983b6..de3339f 100644 --- a/freerelay/control_plane/learner/outcome_consumer.py +++ b/freerelay/control_plane/learner/outcome_consumer.py @@ -195,7 +195,7 @@ async def acknowledge(self, message_ids: list[str]) -> int: count = await self._redis.xack(self._stream, self._group, *message_ids) logger.debug("acked_outcomes count=%d", count) return count - except (json.JSONDecodeError, ValueError, TypeError): + except redis.asyncio.ResponseError: logger.exception("acknowledge_error") return 0 From 9d8368d96eb4ee7bc837f3939d8bab68011f0069 Mon Sep 17 00:00:00 2001 From: Zo Bot Date: Thu, 28 May 2026 23:33:32 +0000 Subject: [PATCH 08/14] =?UTF-8?q?narrow=20exception=20handlers=20in=20poli?= =?UTF-8?q?cy=20publisher=20from=20Exception=20to=20specific=20types=20?= =?UTF-8?q?=E2=80=94=20publish/rollback/snapshot/delete/subscribe=20use=20?= =?UTF-8?q?Redis=20ops=20(ResponseError),=20load=5Fcurrent=20and=20history?= =?UTF-8?q?=20use=20(json.JSONDecodeError,=20ResponseError),=20and=20callb?= =?UTF-8?q?ack=20uses=20(json.JSONDecodeError,=20ValueError,=20TypeError)?= =?UTF-8?q?=20=E2=80=94=20catching=20all=20Exception=20masked=20these=20sp?= =?UTF-8?q?ecific=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../control_plane/learner/policy_publisher.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/freerelay/control_plane/learner/policy_publisher.py b/freerelay/control_plane/learner/policy_publisher.py index 1928893..5187bf6 100644 --- a/freerelay/control_plane/learner/policy_publisher.py +++ b/freerelay/control_plane/learner/policy_publisher.py @@ -85,7 +85,7 @@ async def publish( ) return version - except Exception: + except redis.asyncio.ResponseError: logger.exception("policy_publish_error") raise @@ -96,7 +96,7 @@ async def load_current(self) -> dict[str, Any] | None: if raw is None: return None return json.loads(raw) - except (json.JSONDecodeError, Exception): + except (json.JSONDecodeError, redis.asyncio.ResponseError): logger.exception("policy_load_error") return None @@ -105,7 +105,7 @@ async def get_version_history(self, count: int = 10) -> list[dict[str, Any]]: try: entries = await self._redis.lrange(POLICY_HISTORY_KEY, 0, count - 1) return [json.loads(e) for e in entries] - except Exception: + except redis.asyncio.ResponseError: logger.exception("policy_history_error") return [] @@ -127,7 +127,7 @@ async def rollback(self, target_version: str) -> bool: logger.info("policy_rollback version=%s", target_version) return True - except Exception: + except redis.asyncio.ResponseError: logger.exception("rollback_error") return False @@ -143,7 +143,7 @@ async def snapshot_version(self, version: str) -> None: versioned_key = f"freerelay:policy:versions:{version}" await self._redis.set(versioned_key, raw, ex=86400 * 7) # 7 day TTL logger.debug("policy_version_snapshot version=%s", version) - except Exception: + except redis.asyncio.ResponseError: logger.exception("snapshot_version_error") async def subscribe_to_updates(self, callback: Any) -> None: @@ -162,9 +162,9 @@ async def subscribe_to_updates(self, callback: Any) -> None: try: policy = json.loads(message["data"]) await callback(policy) - except Exception: + except (json.JSONDecodeError, ValueError, TypeError): logger.exception("policy_callback_error") - except Exception: + except redis.asyncio.ResponseError: logger.exception("policy_subscribe_error") finally: await pubsub.unsubscribe(POLICY_CHANNEL) @@ -177,6 +177,6 @@ async def delete_policy(self) -> bool: if deleted: logger.info("policy_deleted") return bool(deleted) - except Exception: + except redis.asyncio.ResponseError: logger.exception("policy_delete_error") return False From fc67b84459115cc13ba4b4721a9cc848424bd665 Mon Sep 17 00:00:00 2001 From: Zo Bot Date: Tue, 2 Jun 2026 09:39:22 +0000 Subject: [PATCH 09/14] =?UTF-8?q?narrow=20condition=20eval=20exception=20f?= =?UTF-8?q?rom=20Exception=20to=20(SyntaxError,=20NameError,=20TypeError,?= =?UTF-8?q?=20ValueError)=20=E2=80=94=20eval=20raises=20SyntaxError=20for?= =?UTF-8?q?=20malformed=20expressions,=20NameError=20for=20undefined=20var?= =?UTF-8?q?iables,=20TypeError=20for=20type=20mismatches,=20and=20ValueErr?= =?UTF-8?q?or=20for=20invalid=20operations;=20catching=20all=20Exception?= =?UTF-8?q?=20masked=20these=20specific=20evaluation=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freerelay/core/routing/policy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freerelay/core/routing/policy.py b/freerelay/core/routing/policy.py index dadb661..9efb249 100644 --- a/freerelay/core/routing/policy.py +++ b/freerelay/core/routing/policy.py @@ -21,7 +21,7 @@ def _eval_condition_context(condition: str, context: dict[str, Any]) -> bool: return False try: return bool(eval(condition, {"__builtins__": {}}, context)) - except Exception: + except (SyntaxError, NameError, TypeError, ValueError): logger.warning("Failed to evaluate condition: %s", condition) return False From 1e8648bc9edc834b2c8ec45ccd330fb3d71483ee Mon Sep 17 00:00:00 2001 From: Zo Bot Date: Tue, 2 Jun 2026 09:46:36 +0000 Subject: [PATCH 10/14] =?UTF-8?q?narrow=20capability=20matrix=20load=20exc?= =?UTF-8?q?eption=20from=20Exception=20to=20(OSError,=20yaml.YAMLError,=20?= =?UTF-8?q?ValueError)=20=E2=80=94=20open=20raises=20OSError=20for=20file?= =?UTF-8?q?=20I/O=20failures,=20yaml.safe=5Fload=20raises=20YAMLError=20fo?= =?UTF-8?q?r=20malformed=20YAML,=20and=20model=5Fvalidate=20raises=20Value?= =?UTF-8?q?Error=20for=20invalid=20schema;=20catching=20all=20Exception=20?= =?UTF-8?q?masked=20these=20specific=20file=20and=20parsing=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freerelay/core/routing/engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freerelay/core/routing/engine.py b/freerelay/core/routing/engine.py index 2a17456..aed44aa 100644 --- a/freerelay/core/routing/engine.py +++ b/freerelay/core/routing/engine.py @@ -163,7 +163,7 @@ def _load_capability_matrix(self, settings: Settings) -> CapabilityMatrix | None matrix = CapabilityMatrix.model_validate(data) logger.info("Loaded capability matrix: %d models", len(matrix.models)) return matrix - except Exception: + except (OSError, yaml.YAMLError, ValueError): logger.exception("Failed to load capability matrix: %s", path) return None From 089b1e6295cae2d51879213e223f577bfa0ad24c Mon Sep 17 00:00:00 2001 From: Zo Bot Date: Tue, 2 Jun 2026 15:34:15 +0000 Subject: [PATCH 11/14] =?UTF-8?q?narrow=20exception=20handlers=20in=20sema?= =?UTF-8?q?ntic=20cache,=20executor,=20and=20Supabase=20logger=20=E2=80=94?= =?UTF-8?q?=20model=5Fvalidate=5Fjson=20raises=20(ValueError,=20TypeError)?= =?UTF-8?q?,=20LSH=20query=20raises=20(TypeError,=20ValueError,=20KeyError?= =?UTF-8?q?),=20execute=5Fhedged=20catches=20(ProviderError,=20ValueError,?= =?UTF-8?q?=20TypeError),=20Supabase=20execute=20raises=20(OSError,=20Valu?= =?UTF-8?q?eError,=20TypeError);=20catching=20all=20Exception=20masked=20t?= =?UTF-8?q?hese=20specific=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freerelay/core/execution/executor.py | 2 +- freerelay/core/intelligence/cache.py | 6 +++--- freerelay/core/observability/supabase_logger.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/freerelay/core/execution/executor.py b/freerelay/core/execution/executor.py index 085e053..83ad4b3 100644 --- a/freerelay/core/execution/executor.py +++ b/freerelay/core/execution/executor.py @@ -156,7 +156,7 @@ async def execute_hedged( for _name, circuit in circuits.items(): await circuit.record_success() return response - except Exception as e: + except (ProviderError, ValueError, TypeError) as e: # Record failure on all involved circuit breakers for _name, circuit in circuits.items(): status = e.status_code if isinstance(e, ProviderError) else None diff --git a/freerelay/core/intelligence/cache.py b/freerelay/core/intelligence/cache.py index 3cdc32a..060dbff 100644 --- a/freerelay/core/intelligence/cache.py +++ b/freerelay/core/intelligence/cache.py @@ -133,7 +133,7 @@ def lookup(self, request: ChatCompletionRequest) -> ChatCompletionResponse | Non if entry and (time.time() - entry.created_at) < entry.ttl: try: return ChatCompletionResponse.model_validate_json(entry.response_json) - except Exception: + except (ValueError, TypeError): del self._entries[key] return None @@ -153,9 +153,9 @@ def lookup(self, request: ChatCompletionRequest) -> ChatCompletionResponse | Non return ChatCompletionResponse.model_validate_json( candidate.response_json ) - except Exception: + except (ValueError, TypeError): continue - except Exception: + except (TypeError, ValueError, KeyError): pass return None diff --git a/freerelay/core/observability/supabase_logger.py b/freerelay/core/observability/supabase_logger.py index 73540aa..9ec7cf5 100644 --- a/freerelay/core/observability/supabase_logger.py +++ b/freerelay/core/observability/supabase_logger.py @@ -29,5 +29,5 @@ def log(self, record: OutcomeRecord) -> None: "schema_pass": record.schema_pass, "notes": record.notes }).execute() - except Exception as e: + except (OSError, ValueError, TypeError) as e: logger.error(f"Failed to log usage to Supabase: {e}") From 1e7b14ca970494ea241de19f49d70b3ff80ce42a Mon Sep 17 00:00:00 2001 From: Zo Bot Date: Tue, 2 Jun 2026 15:37:55 +0000 Subject: [PATCH 12/14] =?UTF-8?q?narrow=20exception=20handlers=20in=20Free?= =?UTF-8?q?Relay=20main=20API=20module=20=E2=80=94=20ORJSONResponse=20impo?= =?UTF-8?q?rt=20raises=20ImportError,=20request.body()=20raises=20OSError,?= =?UTF-8?q?=20model=5Fvalidate=5Fjson=20raises=20(ValueError,=20TypeError)?= =?UTF-8?q?;=20catching=20all=20Exception=20masked=20these=20specific=20fa?= =?UTF-8?q?ilures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freerelay/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freerelay/main.py b/freerelay/main.py index 29c1e35..23f3423 100644 --- a/freerelay/main.py +++ b/freerelay/main.py @@ -94,7 +94,7 @@ def create_app() -> FastAPI: from fastapi.responses import ORJSONResponse default_response_class: type[Response] = ORJSONResponse - except Exception: + except ImportError: default_response_class = JSONResponse app = FastAPI( @@ -202,7 +202,7 @@ async def chat_completions(request: Request) -> Response: try: body = await request.body() - except Exception: + except OSError: return JSONResponse( status_code=400, content=ChatCompletionResponse.error_body("Invalid JSON body", 400), @@ -210,7 +210,7 @@ async def chat_completions(request: Request) -> Response: try: req = ChatCompletionRequest.model_validate_json(body) - except Exception as e: + except (ValueError, TypeError) as e: return JSONResponse( status_code=400, content=ChatCompletionResponse.error_body(f"Invalid request: {e}", 400), From a91df6bef368e5f1696a106906c17e87928f8c68 Mon Sep 17 00:00:00 2001 From: Zo Bot Date: Tue, 2 Jun 2026 23:41:17 +0000 Subject: [PATCH 13/14] =?UTF-8?q?narrow=20exception=20handlers=20in=20ingr?= =?UTF-8?q?ess=20and=20cancellation=20modules=20=E2=80=94=20Redis=20key=20?= =?UTF-8?q?lookup=20and=20rate=20limit=20checks=20catch=20redis.asyncio.Re?= =?UTF-8?q?disError,=20idempotency=20check=20and=20store=20catch=20RedisEr?= =?UTF-8?q?ror,=20httpx=20request=20cancellation=20catches=20(OSError,=20h?= =?UTF-8?q?ttpx.RequestError)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freerelay/data_plane/execution/cancellation.py | 7 +++++-- freerelay/data_plane/ingress/auth.py | 4 +++- freerelay/data_plane/ingress/idempotency.py | 6 ++++-- freerelay/data_plane/ingress/rate_limit.py | 4 +++- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/freerelay/data_plane/execution/cancellation.py b/freerelay/data_plane/execution/cancellation.py index 8df2515..1254879 100644 --- a/freerelay/data_plane/execution/cancellation.py +++ b/freerelay/data_plane/execution/cancellation.py @@ -14,6 +14,8 @@ from enum import StrEnum from typing import Any +import httpx + logger = logging.getLogger("freerelay.data_plane.cancellation") @@ -112,10 +114,11 @@ async def cancel( if self._httpx_request is not None and self._httpx_client is not None: try: await self._httpx_client.cancel_request(self._httpx_request) - except Exception: + except (OSError, httpx.RequestError) as err: logger.debug( - "Failed to cancel httpx request %s", + "Failed to cancel httpx request %s: %s", self._state.request_id, + err, ) # Cancel timeout task diff --git a/freerelay/data_plane/ingress/auth.py b/freerelay/data_plane/ingress/auth.py index ec90d0e..228ac41 100644 --- a/freerelay/data_plane/ingress/auth.py +++ b/freerelay/data_plane/ingress/auth.py @@ -14,6 +14,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING +import redis.asyncio + if TYPE_CHECKING: from redis.asyncio import Redis @@ -101,7 +103,7 @@ async def _lookup_redis(self, key_hash: str) -> str | None: result.decode("utf-8") if isinstance(result, bytes) else str(result) ) return None - except Exception: + except redis.asyncio.RedisError as err: logger.exception("Redis key lookup failed for hash %s", key_hash[:8]) return None diff --git a/freerelay/data_plane/ingress/idempotency.py b/freerelay/data_plane/ingress/idempotency.py index 45a1951..fb161e7 100644 --- a/freerelay/data_plane/ingress/idempotency.py +++ b/freerelay/data_plane/ingress/idempotency.py @@ -12,6 +12,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any +import redis.asyncio + if TYPE_CHECKING: from redis.asyncio import Redis @@ -69,7 +71,7 @@ async def check(self, request_id: str) -> dict[str, Any] | None: if self._redis is not None: try: return await self._check_redis(request_id) - except Exception: + except redis.asyncio.RedisError: logger.exception("Redis idempotency check failed, using fallback") return self._check_memory(request_id) @@ -123,7 +125,7 @@ async def store(self, request_id: str, response: dict[str, Any]) -> bool: if self._redis is not None: try: return await self._store_redis(request_id, data) - except Exception: + except redis.asyncio.RedisError: logger.exception("Redis idempotency store failed, using fallback") return self._store_memory(request_id, data) diff --git a/freerelay/data_plane/ingress/rate_limit.py b/freerelay/data_plane/ingress/rate_limit.py index 500aa71..b4dbec9 100644 --- a/freerelay/data_plane/ingress/rate_limit.py +++ b/freerelay/data_plane/ingress/rate_limit.py @@ -12,6 +12,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING +import redis.asyncio + if TYPE_CHECKING: from redis.asyncio import Redis @@ -138,7 +140,7 @@ async def check_rate_limit( if self._redis is not None: try: return await self._check_redis(namespace, limit) - except Exception: + except redis.asyncio.RedisError: logger.exception("Redis rate limit check failed, using fallback") return self._fallback.check(namespace, limit, self._window) From 53e89760be820c2b98b011e0a606166f2cca82fd Mon Sep 17 00:00:00 2001 From: Zo Bot Date: Fri, 19 Jun 2026 21:40:25 +0000 Subject: [PATCH 14/14] narrow the bare except Exception in provider token status and model fetch get_codex_token_status tries to decode the ChatGPT OAuth JWT to surface expiry and plan_type, then falls back to "Token found (unable to decode JWT)" if anything goes wrong. The body only does operations on a string JWT: token.split --- freerelay/providers/codex.py | 2 +- freerelay/providers/opencode.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/freerelay/providers/codex.py b/freerelay/providers/codex.py index 1789796..e513768 100644 --- a/freerelay/providers/codex.py +++ b/freerelay/providers/codex.py @@ -244,7 +244,7 @@ def get_codex_token_status() -> dict[str, object]: else "Token expired. Re-authenticate with 'openclaw configure'." ), } - except Exception: + except (TypeError, ValueError): pass return { diff --git a/freerelay/providers/opencode.py b/freerelay/providers/opencode.py index 73b1861..6bedbbf 100644 --- a/freerelay/providers/opencode.py +++ b/freerelay/providers/opencode.py @@ -166,7 +166,11 @@ async def fetch_opencode_models(api_key: str = "") -> list[dict[str, str]]: } ) return models - except Exception: + except httpx.RequestError: + pass + except (ValueError, TypeError): + pass + except AttributeError: pass # Fallback: return known free model