feat(autoland): re-arm an escalated PR — PR-scoped, supersedable terminality #272
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab!272
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "afk/188"
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?
Closes #188
An
outcome='escalated'run made its PR invisible to the autoland poller forever, and the gate keyed on(repo_id, branch). Sinceafk/<N>derives from the issue number, a requeued issue reused its branch and the brand-new PR was invisible from birth — a PR never validated, let alone escalated. Keying on the PR number alone would not have been the fix either: the human-fixes-it and manual-description cases are the same PR. All three failed silently — the pollercontinued at the gate with no log line and nothing in the SPA.The reframing this implements: escalation is a statement about a moment ("as of these N attempts, agents could not finish this"), not a permanent property of a thing. A human commit or a fresh set of instructions is new information that invalidates the statement.
What landed
autoland_rearmsrecord (migration 0022).store.RearmPullwrites the supersession instant and zeroes the PR'sfix/escalatebudgets in one transaction. Both halves together, because clearing terminality while leaving the budget spent would escalate again on the first rejection — the worst outcome, since it reads as the re-arm having silently failed.ended_at, or the escalate marker comment'sCreatedAt. Escalated rows stay history, never deleted or rewritten — supersession, not erasure. A fresh escalation after a re-arm is terminal again, indefinitely repeatable.runs.pull_number;autoland_attemptsre-keys to(repo_id, pull_number, kind). A new PR on a reused branch starts virgin with no re-arm needed.DropSupersededEscalationsis the poller's one supersession mechanism, feeding the words fold, the marker half ofEscalated, and the fix run's work order. Filtering the comments rather than just the words is what makes it whole —RejectionContextre-derives the words internally, so a word-only filter would spawn a fix run with an empty work order. There is a test that fails exactly that way if the filter is dropped from that one call site.POST /api/v1/repos/{id}/autoland/pulls/{pull}/rearm, consumed by a History-page SPA action (which also states the consequence, not just theescalatedchip) and bylab autoland rearm.DecideAutolandgains no new states or fields — only the derivation of itsEscalatedinput changed.The security boundary
No
labctlverb exists, deliberately. Escalation means "agents could not finish this"; an agent able to lift its own terminal hand-off would make the bound decorative.internal/agentapi,internal/labctl, andcmd/labctlare untouched by this PR (git diffover them is empty) — the absence there is the feature. The operator verb lives oncmd/labwith a PAT.Review notes — two defects caught during review, worth knowing about
lab autoland rearm -hleaked the operator PAT.-tokendefaults toLAB_PAT, and Go'sflagpackage prints default values in its usage — so-hunder a normal operator environment printed the token to stderr, into scrollback and CI logs. Fixed by overridingfs.Usage;TestRunAutolandRearmUsageNeverPrintsTheTokencovers all six paths that can reach a usage block.pull_numberwasomitempty, contradictingrunResponse's own pinned "every key, always" rule two lines above it. Changed to a bare nullable key matchingissue_number, and the SPA type isnumber | nullaccordingly.Verification
Go:
go build ./...,go vet ./...,gofmt -lclean,go test ./... -count=1green (47 packages). Migration 0022 proven Up → Down → Up against a real sqlite database with DML exercising the PK, the CHECK, and both FK cascades.Not verified: the SPA. No JavaScript runtime exists in this worktree — no node, npm, bun, deno, nix, or container runtime — so
npm run lint,npm run format:check,npm test, andnpm run build(which is wheretsc --noEmitruns) could not be executed. Theweb/changes were reviewed by hand instead: every typedRun/Instancefixture updated (Instance extends Run, so all of them are required),ErrorBanner's default export and prop shape checked against its definition,createLiveResource's[resource, { refetch }]return checked against its overloads, all imports confirmed used, added lines confirmed within the 100-columnprintWidth, and the new route test matched againstRepoCRs.test.tsx's established mount convention. That is review, not proof — please let CI run theweb/job before merging.Docs
ADR-0048 gains an
Amended by issue #188paragraph superseding the "permanent-terminality gate" and "re-entry ... is a manual, human decision" claims. CONTEXT.md's Escalation entry names re-arm as the second re-entry path,_Avoid_line untouched.[autoland] verdict: pass
Validated against the validation core. CI
ci / nativegreen on head32e96fb(run 376, 7m41s), plus the full gate re-run locally: eslint, prettier --check, vitest 990/990, tsc --noEmit + vite build, go build/test -tags ui, golangci-lint 2.12.2 (0 issues), and the untagged go build/vet/gofmt/test suite (the nix gate is path-gated off for this diff). Title is Conventional Commits; body carries Closes #188 matching head afk/188; 1 ahead / 0 behind main, no conflict. Diff scope matches the issue brief, including the security-relevant criterion: no labctl (run-token) rearm verb exists — the internal/agentapi change is comment-only, correcting a stale description of permanent terminality. Lander pushed one formatting-only commit (32e96fb) to fix the prettier violation in web/src/routes/History.test.tsx:93 that was the sole CI failure.