autoland: escalation is permanently terminal with no way back in — re-arm a PR the humans have moved on #188
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab#188
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up from landing #187 (issue #182). Not a regression in that PR — the gate works as designed; the design has no exit.
Problem
An
outcome='escalated'run makes its PR invisible to the autoland poller forever (EscalatedRunOnBranch,internal/store/afk.go). The docs say re-entry is "exactly one path — a human running the interactive land-pr skill." Three cases break that:(repo_id, branch)with no PR or time scoping, andafk/<N>is derived from the issue number. Discard the escalated run, let a fresh AFK run re-claim the issue, and the brand-new claim PR is invisible from birth — a PR that has never been validated, let alone escalated.All three fail silently:
autolandCandidatescontinues at the gate with no log line, no marker on the new PR, nothing in the SPA. The symptom is "autoland just ignores this PR" with no diagnosis path short of reading the runs table.Why keying on the PR number is not the fix
The obvious fix — key terminality on the PR, not the branch — only fixes case 1. Cases 2 and 3 are the same PR.
The real problem is that escalation is modelled as a permanent property of a thing, when it is a statement about a moment: "as of these N attempts, agents could not finish this." A human commit or a fresh set of instructions is new information that invalidates the statement, and nothing in the design can express that. A better key makes the gate more accurate, not less permanent.
What to build
A re-arm operation, not an un-escalate:
fix/escalatecounters inautoland_attempts(added by #187, migration 0015). Both halves, together: clearing terminality while leaving the fix budget spent would escalate again on the first rejection — the worst outcome, because it looks like the clear silently did not work.Open question — the surface
A product call, not an implementation detail. Options:
labctl/labCLI verbWorth deciding alongside: should re-arm be implicit on some signal (a new human commit on the head branch? the issue flipping back to
ready-for-agent?) rather than an explicit gesture. Implicit is friendlier but re-introduces the auto-requeue smell ADR-0024 forbids — the point of the fix-forward design is that re-engagement carries new information, and "a human pushed something" is arguably exactly that signal. Needs a decision before implementation.Notes
Agent Brief
Category: enhancement
Summary: Add an explicit, human-triggered re-arm operation that returns an escalated PR to autoland's view (terminality cleared and attempt budgets restored), scope escalation terminality to the PR rather than the branch, and log the gate's suppression. Product decisions settled during triage: re-arm is exposed on both the SPA and the operator CLI; re-arm is explicit only — no implicit re-arm on human pushes or label flips.
Current behavior:
Escalation is permanently terminal. The autoland poller treats a PR as escalated when either of two sources says so: an
outcome='escalated'run row for the(repo, branch)pair (EscalatedRunOnBranchin the store), or anescalateverdict marker comment at any position in the PR's comment stream (unlikereject/pass/fix-done, which fold last-word-wins). WhenPullVerdictState.Escalatedis set,DecideAutolandreturns no action and the poller silently skips the pull — no log line, no SPA marker. Because the run-row half keys on branch (not PR) andafk/<N>branches derive from issue numbers, a requeued issue's brand-new PR is invisible from birth. Fix/escalate budgets live in theautoland_attemptstable keyed(repo, branch, kind), so a reused branch also inherits its spent budgets. Re-entry today is exactly one path: a human running the interactive land-pr skill.Desired behavior:
MaxFixAttemptsandMaxEscalateAttemptsbudgets, no terminality. Re-arm and budget reset are one atomic operation — clearing terminality while leaving budgets spent would re-escalate on the first rejection and read as a silent failure of the re-arm.escalatemarker comment terminal forever. Re-arm must record a durable supersession moment for the PR, and the escalated fold becomes: terminal iff an escalation signal exists after the last re-arm — for the run-row half by comparing against the run's timestamp, for the comment half by comparing against the marker comment'sCreatedAt(already ontracker.Comment). A fresh escalation after a re-arm is terminal again, indefinitely repeatable. Escalated run rows are history — never deleted or rewritten; supersession, not erasure.labctl): escalation's meaning is "agents could not finish this", and an agent able to lift its own terminal hand-off would make the bound decorative. The SPA should also stop rendering the suppression invisibly wherever it already shows run/PR state — at minimum the escalated state should be visible where the re-arm action lives.Key interfaces:
EscalatedRunOnBranch(store) — becomes a PR-scoped, re-arm-aware query (rename accordingly); callers pass the pull number they already hold.PullVerdictState/DecideAutoland— theEscalatedinput's derivation changes (supersession fold); the decision function itself should not need new states.VerdictWords/ the poller's escalated OR — the comment half moves from "containsescalateanywhere" to "escalate not superseded by a later re-arm".autoland_attempts— keying gains PR scoping; re-arm zeroes (or equivalently re-baselines) thefixandescalaterows for that PR. New migration; follow the existing goose migration conventions including the named-constraint discipline.lab) verb — all three converge on the same store operation.Acceptance criteria:
labctl(run-token) verb exists for it.Out of scope:
ready-for-humanflip, push notification) or toMaxEscalateAttemptssemantics.