fix(runner): a committing agent's work is no longer discarded as scaffold-only #407

Merged
toasterson merged 1 commit from fix/committing-agent-work-discarded into main 2026-07-28 19:02:35 +00:00
Owner

Regression repair for #396, which I merged earlier today.

#396 added a content gate to open_or_update_pr: a scaffold-only staged diff yields no branch and no PR, so a poisoned round-1 branch cannot be inherited by the next dispatch. That gate is right. What it reads is wrong.

It gates on git::capture_diff, which stages the working tree. The forcing prompt asks the agent to commit its own work, and opencode does. Once it has, add -A finds nothing left to stage, the diff is empty, and a completed turn is classified scaffold-only — so the branch is never pushed and the work is thrown away.

head_ahead_of_base exists precisely for this case; its doc comment describes the exact failure, observed 2026-07-25. But the gate returns early, so it is unreachable.

Measured

Four consecutive sessions on 2026-07-28, all stop_reason=end_turn, all logging skipping commit/push/PR: staged diff is scaffold-only … bytes=0 while their worktrees each held a real commit one ahead of base:

17:53  WI-366            dispatches that fail before the session starts
18:26  WI-352            CI lints against an unpinned toolchain
18:33  WI-387            distinguish an empty turn from a failed turn
18:36  solstice-ci WI-5  image push reads the wrong filename

WI-387's lost turn was ~1000 events: a TurnOutcome enum on the witness, migration 082, activity-event plumbing and 8 new tests. The agent reported "all 323 unit tests pass". I recovered that commit from the runner's worktree volume — it is #406.

The change

When nothing is staged, the content gate takes its input from the branch (git::diff_against_base, origin/<base>..HEAD) instead of the working tree.

Both gates still apply, to the same content:

  • an agent that commits only .anima/ scaffold is still skipped;
  • an unclean turn is still skipped even when the agent committed.

Those are the two cases #396 exists to catch, and each has a regression test alongside the positive case.

Verification

cargo check --workspace --all-targets clean; cargo test -p anima-runner 83 passed.

Deploy note: this is runner-side. It needs the compose fleet image rebuilt and the runners recycled — the k8s roll does not carry it.

🤖 Generated with Claude Code

Regression repair for #396, which I merged earlier today. #396 added a content gate to `open_or_update_pr`: a scaffold-only staged diff yields no branch and no PR, so a poisoned round-1 branch cannot be inherited by the next dispatch. That gate is right. **What it reads is wrong.** It gates on `git::capture_diff`, which stages the **working tree**. The forcing prompt asks the agent to commit its own work, and opencode does. Once it has, `add -A` finds nothing left to stage, the diff is empty, and a completed turn is classified scaffold-only — so the branch is never pushed and the work is thrown away. `head_ahead_of_base` exists precisely for this case; its doc comment describes the exact failure, observed 2026-07-25. But the gate returns early, so it is unreachable. ## Measured Four consecutive sessions on 2026-07-28, all `stop_reason=end_turn`, all logging `skipping commit/push/PR: staged diff is scaffold-only … bytes=0` while their worktrees each held a real commit one ahead of base: ``` 17:53 WI-366 dispatches that fail before the session starts 18:26 WI-352 CI lints against an unpinned toolchain 18:33 WI-387 distinguish an empty turn from a failed turn 18:36 solstice-ci WI-5 image push reads the wrong filename ``` WI-387's lost turn was ~1000 events: a `TurnOutcome` enum on the witness, migration 082, activity-event plumbing and 8 new tests. The agent reported "all 323 unit tests pass". I recovered that commit from the runner's worktree volume — it is #406. ## The change When nothing is staged, the content gate takes its input from the branch (`git::diff_against_base`, `origin/<base>..HEAD`) instead of the working tree. Both gates still apply, to the same content: - an agent that commits only `.anima/` scaffold is still skipped; - an unclean turn is still skipped even when the agent committed. Those are the two cases #396 exists to catch, and each has a regression test alongside the positive case. ## Verification `cargo check --workspace --all-targets` clean; `cargo test -p anima-runner` 83 passed. **Deploy note:** this is runner-side. It needs the compose fleet image rebuilt and the runners recycled — the k8s roll does not carry it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(runner): a committing agent's work is no longer discarded as scaffold-only
All checks were successful
CI / conflict-check (push) Successful in 30s
CI / docker (push) Successful in 9m9s
3df189a313
#396 added a content gate to `open_or_update_pr`: a scaffold-only staged diff
yields no branch and no PR, so a poisoned round-1 branch cannot be inherited by
the next dispatch. The gate reads `git::capture_diff`, which stages the WORKING
TREE — and the forcing prompt asks the agent to commit its own work, which
opencode does. Once it has, `add -A` finds nothing left to stage and the diff is
empty, so a completed turn is classified scaffold-only and thrown away.

`head_ahead_of_base` exists precisely for this case — its doc comment describes
the exact failure, observed 2026-07-25 — but the gate returns early, so it is
unreachable.

Measured on 2026-07-28, four consecutive sessions, all `stop_reason=end_turn`,
all logging `skipping commit/push/PR: staged diff is scaffold-only … bytes=0`
while their worktrees each held a real commit one ahead of base:

    17:53  WI-366   dispatches that fail before the session starts
    18:26  WI-352   CI lints against an unpinned toolchain
    18:33  WI-387   distinguish an empty turn from a failed turn
    18:36  solstice-ci WI-5  image push reads the wrong filename

WI-387's lost turn was ~1000 events: a `TurnOutcome` enum on the witness,
migration 082, activity-event plumbing and 8 new tests. The agent reported "all
323 unit tests pass"; none of it was pushed.

When nothing is staged, the content gate now takes its input from the branch
(`git::diff_against_base`, `origin/<base>..HEAD`) instead of the working tree.
Both gates still apply to the same content: an agent that commits only `.anima/`
scaffold is still skipped, and an unclean turn is still skipped even when
committed — the two cases #396 exists to catch.

Three regression tests pin all three shapes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
toasterson deleted branch fix/committing-agent-work-discarded 2026-07-28 19:02:35 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
toasterson/Anima!407
No description provided.