Operant Studio
OPER-156

OPER: teach agent-automerge to update-branch when behind, before merging

Band
MEDIUM
Suggested
Justin Cooke

Body

---
feature_id: FEAT-fleet-gate-auto-update-behind
filing_template_version: v1
decision_memo: docs/pdt/2026.09.09-observation-auto-update.md
blocked_by:
---

## Context

On 2026.09.09 the fleet shipped OPER-148 (#433/#434/#435) and OPER-150 (#440) into main.
That pushed main forward while OPER-146 PR 2/4 (#437) and OPER-151 PR 1/3 (#441)
were still open and CI-green.  Both PRs immediately went to `mergeable_state=behind`
under our strict branch protection.  Neither `agent-automerge.yml` nor
`stacked-pr-rescue.yml` responds to that state -- `stacked-pr-rescue` only
retargets stacked children after a base merges, and `agent-automerge` will not
merge a `behind` PR.  A human had to hit GitHub's Update Branch API on each PR
to unblock the fleet.

This is a governance gap.  The point of the agent fleet is unattended forward
progress; a green PR that sits behind main for hours because a sibling merged
is the same class of stall as the `[BLOCKED]` prefix problem OPER-D5 fixed for
drafts.

## Observed sequence

1. Agent opens PR against `agent/parent` or `main`, CI turns green.
2. Sibling PR merges to main.
3. Target PR flips to `mergeable_state=behind` (strict protection).
4. `agent-automerge` re-runs on push-to-main.  It has no `behind` handler, so
   it silently skips.
5. PR sits until a human calls PUT /pulls/{n}/update-branch.

## Acceptance Criteria

1. New workflow (or new job in `agent-automerge.yml`) fires on `push` to `main`
   and on `pull_request` `synchronize`.
2. The job enumerates open PRs whose head branch matches `agent/*` (fleet-owned).
3. For each such PR where `mergeable_state == 'behind'` AND every required
   status check is currently `success` AND the PR is not a draft AND the title
   does not start with `[BLOCKED]`, the job calls `PUT /pulls/{n}/update-branch`.
4. If the update-branch call returns a merge conflict (422), post a sticky
   comment `Fleet: auto-update-behind hit a merge conflict; requires rebase.`
   and stop (do not retry per-run).
5. Bounded concurrency: at most 5 update-branch calls per workflow invocation
   to keep API cost low; queue the rest for the next `push` event.
6. Self-test script `scripts/auto-update-behind-selftest.sh` (KNUCK-A032 style)
   asserts that (a) the enumeration excludes draft PRs, (b) the enumeration
   excludes PRs with any failing required check, (c) the enumeration excludes
   PRs whose title starts with `[BLOCKED]`, (d) the enumeration includes
   PRs whose only failing state is `behind`.
7. `docs/task-state-machine.md` (or the relevant fleet doc) records the new
   gate under `Agent-fleet gates -> Post-merge sibling catch-up`.

## Verification

- `gh workflow view Auto-update-behind` shows the new workflow.
- After a merge to main lands, any qualifying `agent/*` PR reaches
  `mergeable_state != behind` within one workflow run.
- `scripts/auto-update-behind-selftest.sh` passes locally and in CI.

## Notes

- Do not touch human-authored PRs (branch name outside `agent/*`).
- Do not touch PRs against non-main bases (those are managed by
  `stacked-pr-rescue.yml` on parent merge).
- Compose with `agent-automerge.yml`, do not replace it -- the two workflows
  are separate concerns (update-behind vs. merge-when-green).


---
<!-- admission-rejected -->
**Admission rejected (unknown_feature_id):** feature_id=FEAT-fleet-gate-auto-update-behind is not declared in the feature-map.md of any admission-enabled product owned by this team

Attachments

Loading attachments…

Comments

Loading comments…