From 646d00adde1d63a3c5dba02e1fddcee4bc8e4614 Mon Sep 17 00:00:00 2001 From: Tyler Date: Sun, 21 Jun 2026 13:51:42 -0600 Subject: [PATCH] =?UTF-8?q?plan(SP22):=20Task=206=20=E2=80=94=20apply=20Ta?= =?UTF-8?q?sk=204/5=20fixes=20(4xx=20terminal,=20RuntimeError,=20retry=20l?= =?UTF-8?q?ogging)=20preemptively?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-06-21-cyclone-pipeline-agent.md | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/docs/superpowers/plans/2026-06-21-cyclone-pipeline-agent.md b/docs/superpowers/plans/2026-06-21-cyclone-pipeline-agent.md index 009550a..4a460b8 100644 --- a/docs/superpowers/plans/2026-06-21-cyclone-pipeline-agent.md +++ b/docs/superpowers/plans/2026-06-21-cyclone-pipeline-agent.md @@ -1177,7 +1177,8 @@ Add the methods to the `CycloneClient` class: async def clearhouse_submit( self, payer_id: str, tx: str = "837P" ) -> SubmitResult: - assert self._http is not None + if self._http is None: + raise RuntimeError("use `async with CycloneClient(...)`") # Do NOT auto-retry — would double-submit. resp = await self._http.post( "/api/clearhouse/submit", @@ -1190,7 +1191,8 @@ Add the methods to the `CycloneClient` class: return SubmitResult.model_validate(resp.json()) async def scheduler_tick(self) -> TickResult: - assert self._http is not None + if self._http is None: + raise RuntimeError("use `async with CycloneClient(...)`") # Manual tick is idempotent; safe to retry. last_exc: Exception | None = None for attempt in range(MAX_RETRIES): @@ -1200,15 +1202,26 @@ Add the methods to the `CycloneClient` class: ) resp.raise_for_status() return TickResult.model_validate(resp.json()) - except (httpx.HTTPStatusError, httpx.RequestError) as e: + except httpx.HTTPStatusError as e: + if e.response is not None and e.response.status_code < 500: + raise last_exc = e - if attempt < MAX_RETRIES - 1: - await asyncio.sleep(BACKOFF_S[attempt]) - assert last_exc is not None + except httpx.RequestError as e: + last_exc = e + if attempt < MAX_RETRIES - 1: + log.warning( + "api_post_retry", + path="/api/admin/scheduler/tick", + attempt=attempt + 1, wait_s=BACKOFF_S[attempt], + error=str(last_exc), + ) + await asyncio.sleep(BACKOFF_S[attempt]) + assert last_exc is not None # loop body always sets this on the way out raise last_exc async def processed_files(self, since: str) -> list[ProcessedFile]: - assert self._http is not None + if self._http is None: + raise RuntimeError("use `async with CycloneClient(...)`") last_exc: Exception | None = None for attempt in range(MAX_RETRIES): try: @@ -1221,11 +1234,21 @@ Add the methods to the `CycloneClient` class: ProcessedFile.model_validate(f) for f in _extract_items(resp.json()) ] - except (httpx.HTTPStatusError, httpx.RequestError) as e: + except httpx.HTTPStatusError as e: + if e.response is not None and e.response.status_code < 500: + raise last_exc = e - if attempt < MAX_RETRIES - 1: - await asyncio.sleep(BACKOFF_S[attempt]) - assert last_exc is not None + except httpx.RequestError as e: + last_exc = e + if attempt < MAX_RETRIES - 1: + log.warning( + "api_get_retry", + path="/api/admin/scheduler/processed-files", + attempt=attempt + 1, wait_s=BACKOFF_S[attempt], + error=str(last_exc), + ) + await asyncio.sleep(BACKOFF_S[attempt]) + assert last_exc is not None # loop body always sets this on the way out raise last_exc ```