Compare commits

..
4 Commits
Author SHA1 Message Date
dongarandClaude Opus 5.5 3813ad55d6 nisse-review: read code from a git clone kept between runs
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m3s
CI / e2e (pull_request) Successful in 4m5s
Archives were downloaded and unpacked afresh for every run, which doesn't
scale to very large repositories. Each repository is now cloned once, with
its whole history, under scratch/repos/<owner>/<name>/ (head/ the clone,
base/ a worktree when read). A run fetches every branch and the PR's
refs/pull/N/head, checks out the head exactly, and fails if that head is
gone. Only the latest repository is kept for now.

Git runs walled off: none of the host's git configuration, links checked
out as plain files, no hooks, submodules or LFS, only the Gitea URL's
protocol, and the token as an http.extraHeader for that URL in git's
environment only. A stop ends a git command; a cut-short first clone is
removed. The reader never shows .git. GiteaClient.archive is gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-17 20:37:12 +00:00
dongarandClaude Opus 5.5 a85e855fc2 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
2026-10-17 20:37:12 +00:00
dev-agent 52d30ccab9 Merge pull request 'nisse-dev: a VM lost as a helper's work is taken back ends the run' (#161) from dev-delegate-lost-vm into main
CI / checks (push) Successful in 2m42s
CI / e2e (push) Skipped
2026-10-17 20:37:11 +00:00
dongarandClaude Opus 5.5 76d8e36f43 nisse-dev: a VM lost as a helper's work is taken back ends the run
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 2m0s
CI / e2e (pull_request) Successful in 3m54s
The property walk (seed 468, a CI run's moving seed) found it: when the
build VM went out of reach while _delegate moved the helper's work back
onto the milestone's branch, git's HostUnavailable came back as a
GitError, was noted as a fact, and the driver's run went on, calling the
model with its VM gone (no_model_on_a_lost_vm). Now a GitError caused by a
lost VM raises VMUnreachable once the delegation's intents are settled, so
the run ends and the VM is blocked, as for any other lost-VM call (#137).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
2026-10-17 16:00:31 +00:00
2 changed files with 44 additions and 0 deletions

No files matched your search

+5
View File
@@ -2111,6 +2111,7 @@ class LoopAgent:
result = delegation.run( result = delegation.run(
self._store.transcript(item.id, "helper"), brief=brief, branch=branch self._store.transcript(item.id, "helper"), brief=brief, branch=branch
) )
lost: GitError | None = None
try: try:
# The milestone takes the helper's work (or, on failure, only # The milestone takes the helper's work (or, on failure, only
# its own back) as uncommitted changes: harness branches never # its own back) as uncommitted changes: harness branches never
@@ -2122,6 +2123,8 @@ class LoopAgent:
git._git("branch", "-q", "-D", branch) git._git("branch", "-q", "-D", branch)
except GitError as exc: except GitError as exc:
self._fact(item, f"after helper {number}: {exc}") self._fact(item, f"after helper {number}: {exc}")
if isinstance(exc.__cause__, HostUnavailable):
lost = exc # the VM went: the driver's run ends too (#137)
for intent in self._store.pending_intents(): for intent in self._store.pending_intents():
# Cut off rather than finished: discarded all the same, never # Cut off rather than finished: discarded all the same, never
# resumed blindly (L8, L9). # resumed blindly (L8, L9).
@@ -2130,6 +2133,8 @@ class LoopAgent:
and intent.payload.get("branch") == branch and intent.payload.get("branch") == branch
): ):
self._store.abort_intent(intent.id, cause=f"delegation {result.status}") self._store.abort_intent(intent.id, cause=f"delegation {result.status}")
if lost is not None:
raise VMUnreachable(str(lost)) from lost
if result.usable and result.handback is not None: if result.usable and result.handback is not None:
self._fact(item, f"helper {number} finished: {result.handback.render()}") self._fact(item, f"helper {number} finished: {result.handback.render()}")
return f"the helper finished:\n{result.handback.render()}", True return f"the helper finished:\n{result.handback.render()}", True
+39
View File
@@ -338,3 +338,42 @@ def test_a_vm_lost_just_before_the_step_blocks_before_any_model_call(
agent, _ = agent_for(world, project, model, clock=Clock()) agent, _ = agent_for(world, project, model, clock=Clock())
agent.intake() agent.intake()
assert list(blocks(world).values()) == ["unreachable"] assert list(blocks(world).values()) == ["unreachable"]
def test_a_vm_lost_as_a_helper_hands_back_ends_the_drivers_run(
world: dict[str, Any], project: str, monkeypatch: pytest.MonkeyPatch
) -> None:
# Found by the property walk (seed 468): taking the helper's work back
# onto the milestone's branch failed for the lost VM, was noted as a git
# error, and the driver's model was called with the VM gone.
from test_loop_help import Spy, hand_back, helpers
faults = Faults(monkeypatch, world["host"])
class Leaving(Spy):
def complete(self, events, tools, model, system=None) -> list[Any]:
out = super().complete(events, tools, model, system)
if not self.turns: # gone as its work is taken back
faults.drop_on = "checkout -q -f -B"
return out
helper = Leaving(
[call("write_file", path="helped.py", content="H = 1\n")], [hand_back()]
)
model = ScriptedModel(
ready(),
RESTATE,
[
call("start_milestone", name="m"),
call("delegate", task="write helped.py"),
],
[call("run_command", command="echo never")], # never asked for
)
agent, transport = agent_for(
world, project, model, clock=Clock(), helpers=helpers(helper)
)
agent.intake()
say(transport, world, "go")
assert list(blocks(world).values()) == ["unreachable"]
assert the_item(world).position == READY
assert len(model.turns) == 1 # the driver's model wasn't called again