nisse-review: a stop ends a review in progress; quieter repo failures
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m22s
CI / e2e (pull_request) Successful in 4m20s

SIGTERM used to stop only new rounds: a review in progress ran on until
systemd killed it 90s later. Now the scheduler's stop sets the driver's
cancel flag, so the run ends at its next turn, and a model call in flight
is broken off (Interruptible, raising Stopped from the signal handler, only
ever inside a model call). The item goes back to ready, with nothing
drafted posted and no failure counted; replies already posted carry their
markers and are not repeated.

intake.repo_failed is logged when a repository starts failing or the reason
changes, and intake.repo_ok when it lists again, not every round.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-17 20:37:12 +00:00
1 parent 52d30ccab9
commit a85e855fc2
8 files changed
+287 -10

No files matched your search

@@ -0,0 +1,51 @@
"""A model call that a stop can break off (L2).
A ``Cancel`` takes effect at the next tool boundary, but one model call can
run for many minutes (``request_timeout_s``), longer than a service manager
waits before it kills the process. ``Interruptible`` wraps the client and
knows when a call is in flight; ``interrupt``, called from the signal
handler, raises ``Stopped`` inside that call so it ends at once.
Only a model call is ever broken off this way, never a post or a write:
outside a call, ``interrupt`` does nothing and the run ends at its next
boundary instead. Python runs signal handlers on the main thread, so this
works when the calls are made there.
"""
from typing import Any
from nisse_core.log.canonical import CanonicalEvent, StoredEvent
from nisse_core.models.llm import LLMClient
class Stopped(BaseException):
"""A model call broken off by a stop. A ``BaseException``, so no handler
meant for failures (``except Exception``, ``except LLMError``) takes it
for one."""
class Interruptible:
def __init__(self, client: LLMClient) -> None:
self._client = client
self._calling = False
def complete(
self,
events: list[StoredEvent],
tools: list[dict[str, Any]] | None,
model: str,
system: str | None = None,
) -> list[CanonicalEvent]:
self._calling = True
try:
return self._client.complete(events, tools, model, system)
finally:
self._calling = False
def interrupt(self) -> None:
"""From a signal handler: breaks off the call in flight, if any."""
if self._calling:
raise Stopped("a stop broke off the model call")
def __getattr__(self, name: str) -> Any:
return getattr(self._client, name)