chore(consistency): sync task status from DB [auto]

Updated by fix-consistency on 2026-09-04:
  - update .custodian-brief.md for rein-aharness

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a06ba0-10aa-7ea0-b20a-4f3fac39efe9
This commit is contained in:
custodian-sync 2026-09-04 11:00:20 +02:00
parent 7641fcde40
commit f01b765668
20 changed files with 1027 additions and 165 deletions

View file

@ -33,6 +33,11 @@ from llm_connect.claude_code import ClaudeCodeAdapter
from llm_connect.exceptions import LLMSubprocessError, LLMTimeoutError
from llm_connect.models import LLMResponse, RunConfig
from rein_aharness.execution_cancel import (
ExecutionCancel,
ExecutionCancelled,
resolve_cancel,
)
from rein_aharness.profiles import ToolProfile, get_profile
# Backward-compatible alias for the seed profile allow-list string.
@ -41,6 +46,29 @@ ALLOWED_TOOLS = get_profile("green-commit-only").allowed_tools
ToolEventCallback = Callable[[dict[str, Any]], None]
def _kill_process(proc: subprocess.Popen[str] | Any) -> None:
poll = getattr(proc, "poll", None)
if callable(poll):
try:
status = poll()
except Exception:
status = None
if isinstance(status, int):
return
kill = getattr(proc, "kill", None)
if callable(kill):
try:
kill()
except Exception:
return
wait = getattr(proc, "wait", None)
if callable(wait):
try:
wait(timeout=2)
except Exception:
return
def _is_tool_event(event: dict[str, Any]) -> bool:
"""True for tool_use/tool_result content blocks and hook lifecycle events.
@ -64,11 +92,13 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
*,
tool_profile: str | ToolProfile = "green-commit-only",
on_tool_event: ToolEventCallback | None = None,
cancel: ExecutionCancel | None = None,
**kwargs,
):
super().__init__(**kwargs)
self._workdir = workdir
self._on_tool_event = on_tool_event
self._cancel = cancel
if isinstance(tool_profile, ToolProfile):
self._profile = tool_profile
else:
@ -105,27 +135,23 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
return response
def _execute_blocking(self, cmd: list[str], prompt: str, timeout: int) -> LLMResponse:
try:
result = subprocess.run(
cmd,
input=prompt,
capture_output=True,
text=True,
timeout=timeout,
cwd=self._workdir,
)
except subprocess.TimeoutExpired as exc:
raise LLMTimeoutError(
f"claude CLI timed out after {timeout}s", cause=exc
) from exc
if result.returncode != 0:
proc = subprocess.Popen(
cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
cwd=self._workdir,
)
stdout, stderr = self._wait_for_process(proc, prompt, timeout)
if proc.returncode != 0:
raise LLMSubprocessError(
f"claude CLI exited with code {result.returncode}",
return_code=result.returncode,
stderr=result.stderr,
f"claude CLI exited with code {proc.returncode}",
return_code=proc.returncode,
stderr=stderr,
)
return LLMResponse(
content=result.stdout,
content=stdout,
model=self._model or "claude-code-cli",
usage={},
finish_reason="stop",
@ -170,12 +196,7 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
proc.stdin.write(prompt)
proc.stdin.close()
reader_thread.start()
try:
returncode = proc.wait(timeout=timeout)
except subprocess.TimeoutExpired as exc:
proc.kill()
proc.wait()
raise LLMTimeoutError(f"claude CLI timed out after {timeout}s", cause=exc) from exc
returncode = self._wait_for_process(proc, None, timeout, communicate=False)
reader_thread.join(timeout=5)
stderr = proc.stderr.read() if proc.stderr else ""
@ -200,6 +221,38 @@ class AgenticClaudeCodeAdapter(ClaudeCodeAdapter):
},
)
def _wait_for_process(
self,
proc: subprocess.Popen[str],
prompt: str | None,
timeout: int,
*,
communicate: bool = True,
) -> Any:
cancel = resolve_cancel(self._cancel)
if cancel is not None:
cancel.check()
cancel.register_process(proc)
try:
if communicate:
result: Any = proc.communicate(input=prompt, timeout=timeout)
else:
result = proc.wait(timeout=timeout)
except subprocess.TimeoutExpired as exc:
_kill_process(proc)
raise LLMTimeoutError(
f"claude CLI timed out after {timeout}s", cause=exc
) from exc
except ExecutionCancelled:
raise
except Exception:
if cancel is not None and cancel.cancelled:
raise ExecutionCancelled(cancel.reason or "cancelled") from None
raise
if cancel is not None:
cancel.check()
return result
@staticmethod
def _handle_stream_event(event: dict[str, Any], text_parts: list[str]) -> None:
if event.get("type") != "assistant":