[eric] browser: log write-tier failures (adapter bug=WARNING, site-reject=INFO) so a systemically-broken fast path isn't silently invisible to ops

This commit is contained in:
ciregenz
2026-07-15 17:10:57 -07:00
parent 449f66057d
commit fe02b4ce91
2 changed files with 20 additions and 2 deletions
+9 -1
View File
@@ -19,6 +19,7 @@ SAFETY (this IS the posture flip away from GET/HEAD-only, so the walls are belt-
"""
import json
import logging
import os
import re
import time
@@ -32,6 +33,8 @@ from typeguard import typechecked
from backend.apps.social_shims.session_source import get_session
logger = logging.getLogger(__name__)
WRITE_METHODS = frozenset({"POST", "PUT", "PATCH", "DELETE"})
# CSRF header a site derives from a cookie (so it survives a fresh borrowed session). Small on
# purpose: most cookie-auth internal APIs need nothing extra; this covers the common header case.
@@ -206,5 +209,10 @@ def replay_write(method: str, url: str, body: Dict[str, Any], origin: str,
try:
status, text = issue_request(method, url, body, headers)
except Exception as e:
# UNEXPECTED: the request itself blew up (network/DNS/TLS), not a same-origin/captured refusal. Fails open to the UI, so log it or the broken route-write tier is invisible.
logger.warning(f"[route-write] {method} {url} request FAILED (fast path broken here): {e}")
return ReplayOutcome(ok=False, error=str(e)[:160], latency_ms=int((time.monotonic() - t0) * 1000))
return outcome_from_response(status, text, int((time.monotonic() - t0) * 1000))
out = outcome_from_response(status, text, int((time.monotonic() - t0) * 1000))
if not out.ok:
logger.info(f"[route-write] {method} {url} rejected by site: HTTP {out.status}")
return out
@@ -11,6 +11,7 @@ Live-validated end to end on Reddit (comment 271ms + reversible delete 246ms, ty
"""
import asyncio
import logging
import os
import time
from typing import Any, Callable, Dict, FrozenSet, List, Tuple
@@ -22,6 +23,8 @@ from typeguard import typechecked
from backend.apps.agents.browser import route_write
from backend.apps.reddit_mcp_shim import reddit_writes
logger = logging.getLogger(__name__)
class WriteResult(BaseModel):
"""The typed outcome of an API-first write. `receipt` is the site's own id/permalink, the
@@ -133,6 +136,13 @@ async def api_write(domain: str, action: str, params: Dict[str, Any]) -> WriteRe
return WriteResult(ok=True, action=action, domain=d,
receipt=receipt_str(receipt),
latency_ms=int((time.monotonic() - t0) * 1000))
except Exception as e:
except reddit_writes.RedditError as e:
# EXPECTED site-side reject (not logged in, rate-limited, bad params): the model sees it and falls back; info, not an alarm. (Future adapters should raise their own recognizable reject type to land here.)
logger.info(f"[api-write] {d}/{action} rejected by the site: {e}")
return WriteResult(ok=False, action=action, domain=d, error=str(e)[:200],
latency_ms=int((time.monotonic() - t0) * 1000))
except Exception as e:
# UNEXPECTED: the adapter code itself threw (a bug, or the site changed shape). It fails open to the UI, so without this WARNING a systemically-broken fast path is INVISIBLE (looks like the tier just isn't used).
logger.warning(f"[api-write] {d}/{action} adapter FAILED unexpectedly (fast path broken here): {e}")
return WriteResult(ok=False, action=action, domain=d, error=str(e)[:200],
latency_ms=int((time.monotonic() - t0) * 1000))