From cde92d3db10fda4e9b1ca5d59608a6eafead221f Mon Sep 17 00:00:00 2001 From: m3taversal Date: Mon, 20 Apr 2026 12:37:28 +0100 Subject: [PATCH] fix: wrap breaker calls in stage_loop to prevent permanent task death MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A transient DB lock in breaker.record_failure() inside an except handler killed the asyncio coroutine permanently — snapshot_cycle died Apr 18 and never recovered. All three breaker call sites now have their own try/except. Also includes HTML injection fix for github_feedback review_text. Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/github_feedback.py | 6 ++++-- teleo-pipeline.py | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/github_feedback.py b/lib/github_feedback.py index d729baf..52dbf2e 100644 --- a/lib/github_feedback.py +++ b/lib/github_feedback.py @@ -125,7 +125,8 @@ async def on_eval_complete(conn, forgejo_pr: int, *, outcome: str, review_text: if outcome == "approved": body = "**Evaluation: Approved**\n\nYour contribution passed automated review and is queued for merge." if review_text: - body += f"\n\n
\nReview details\n\n{review_text[:3000]}\n\n
" + safe_text = review_text[:3000].replace("", "</details>") + body += f"\n\n
\nReview details\n\n{safe_text}\n\n
" elif outcome == "rejected": body = "**Evaluation: Changes Requested**\n\n" if issues: @@ -133,7 +134,8 @@ async def on_eval_complete(conn, forgejo_pr: int, *, outcome: str, review_text: for issue in issues: body += f"- {issue}\n" if review_text: - body += f"\n
\nFull review\n\n{review_text[:3000]}\n\n
" + safe_text = review_text[:3000].replace("", "</details>") + body += f"\n
\nFull review\n\n{safe_text}\n\n
" body += ( "\n\nThe pipeline will attempt automated fixes where possible. " "If fixes fail, the PR will be closed — you're welcome to resubmit." diff --git a/teleo-pipeline.py b/teleo-pipeline.py index ba0080c..95f412b 100644 --- a/teleo-pipeline.py +++ b/teleo-pipeline.py @@ -47,12 +47,21 @@ async def stage_loop(name: str, interval: int, func, conn, breaker: CircuitBreak workers = breaker.max_workers() succeeded, failed = await func(conn, max_workers=workers) if failed > 0 and succeeded == 0: - breaker.record_failure() + try: + breaker.record_failure() + except Exception: + logger.warning("Stage %s: breaker write failed", name) elif succeeded > 0: - breaker.record_success() + try: + breaker.record_success() + except Exception: + logger.warning("Stage %s: breaker write failed", name) except Exception: logger.exception("Stage %s: unhandled error in cycle", name) - breaker.record_failure() + try: + breaker.record_failure() + except Exception: + logger.warning("Stage %s: breaker write failed", name) # Wait for interval or shutdown, whichever comes first try: