Native PR merge can leave a PR closed but unmerged after Forgejo 405 #1170

Closed
opened 2026-07-13 04:13:31 +00:00 by coilyco-ops · 8 comments
Member

A failed ward agent pr merge appears able to leave a PR closed but unmerged. This is a stronger failure mode than a plain transient 405 because it removes the PR from the open queue without landing the branch.

Observed from a director surface on 2026-07-13:

  • ward#1164 was open, mergeable: true, and ward agent pr status coilyco-flight-deck/ward#1164 reported combined status success for all checks.
  • ward agent pr merge coilyco-flight-deck/ward#1164 returned forgejo merge PR returned 405 Method Not Allowed: {"message":"Please try again later","url":"https://forgejo.coilysiren.me/api/swagger"} three times.
  • A later open-PR list no longer contained ward#1164.
  • ward ops forgejo pr view coilyco-flight-deck ward 1164 --query ... then reported state: closed, merged: false, mergeable: true, head be61274cbfb34afd77ee13d5555639b8d067a7af.

Wanted behavior:

  • The native merge command must not treat a server response as complete unless the postcondition proves merged: true.
  • If the server closes a PR without merging it, ward should surface that as a hard failed postcondition with the PR number and head SHA.
  • The director lane needs a recovery path for a closed-unmerged PR, either reopen-and-retry when safe or file a clear salvage issue for the branch.

This blocks the ward issue burn-down loop because a green PR can disappear from the open queue without reducing issue debt on main.

A failed `ward agent pr merge` appears able to leave a PR closed but unmerged. This is a stronger failure mode than a plain transient 405 because it removes the PR from the open queue without landing the branch. Observed from a director surface on 2026-07-13: * `ward#1164` was open, `mergeable: true`, and `ward agent pr status coilyco-flight-deck/ward#1164` reported combined status `success` for all checks. * `ward agent pr merge coilyco-flight-deck/ward#1164` returned `forgejo merge PR returned 405 Method Not Allowed: {"message":"Please try again later","url":"https://forgejo.coilysiren.me/api/swagger"}` three times. * A later open-PR list no longer contained `ward#1164`. * `ward ops forgejo pr view coilyco-flight-deck ward 1164 --query ...` then reported `state: closed`, `merged: false`, `mergeable: true`, head `be61274cbfb34afd77ee13d5555639b8d067a7af`. Wanted behavior: * The native merge command must not treat a server response as complete unless the postcondition proves `merged: true`. * If the server closes a PR without merging it, ward should surface that as a hard failed postcondition with the PR number and head SHA. * The director lane needs a recovery path for a closed-unmerged PR, either reopen-and-retry when safe or file a clear salvage issue for the branch. This blocks the ward issue burn-down loop because a green PR can disappear from the open queue without reducing issue debt on main.
Author
Member

The burn-down session confirms this recovery gap from direct experience. Forgejo never marked the queue-landed PRs as merged after their content reached main by push - every one of the eight landed PRs stayed open with merged=false until the session posted a provenance comment and closed it by hand. That is exactly the closed-but-unmerged shape this issue covers, produced at scale in one evening. Recovery verbs are worth having: reopen, a mark-merged or merged-by-push detection sweep, and a closed-unmerged audit. Root cause context for the 405 that opened this issue lives in ward#1168 - the stale-lock theory is now ruled out and the wedged checker queue is the live suspect.

The burn-down session confirms this recovery gap from direct experience. Forgejo never marked the queue-landed PRs as merged after their content reached main by push - every one of the eight landed PRs stayed open with merged=false until the session posted a provenance comment and closed it by hand. That is exactly the closed-but-unmerged shape this issue covers, produced at scale in one evening. Recovery verbs are worth having: reopen, a mark-merged or merged-by-push detection sweep, and a closed-unmerged audit. Root cause context for the 405 that opened this issue lives in ward#1168 - the stale-lock theory is now ruled out and the wedged checker queue is the live suspect.
Author
Member

Fresh burn-down evidence from 2026-07-14: after ward agent pr merge coilyco-flight-deck/ward#1261 returned Forgejo 405 twice, the PR later became state: closed, merged: false, head 8ec314a624f76221636922b1f2286235f89956b5. The director reopened it through ward ops forgejo pr reopen, which restored state: open but Forgejo still reports mergeable: false. This is the same closed-unmerged postcondition failure this issue tracks, now reproduced on ward#1261.

Fresh burn-down evidence from 2026-07-14: after `ward agent pr merge coilyco-flight-deck/ward#1261` returned Forgejo 405 twice, the PR later became `state: closed`, `merged: false`, head `8ec314a624f76221636922b1f2286235f89956b5`. The director reopened it through `ward ops forgejo pr reopen`, which restored `state: open` but Forgejo still reports `mergeable: false`. This is the same closed-unmerged postcondition failure this issue tracks, now reproduced on ward#1261.
Author
Member

Additional 2026-07-14 detail: PR #1261 was reopened through ward ops forgejo pr reopen, but a later Forgejo PR view again reported state: closed, merged: false, head 8ec314a624f76221636922b1f2286235f89956b5. The issue thread remained open. Reopen alone is not a stable recovery when the underlying branch is conflicted and the broker-backed merge lane is down.

Additional 2026-07-14 detail: PR #1261 was reopened through `ward ops forgejo pr reopen`, but a later Forgejo PR view again reported `state: closed`, `merged: false`, head `8ec314a624f76221636922b1f2286235f89956b5`. The issue thread remained open. Reopen alone is not a stable recovery when the underlying branch is conflicted and the broker-backed merge lane is down.
Author
Member

WARDED_WORKFLOW: reservation-released

release details

Run never started. ward container reap released container engineer-codex-ward-1170 (--harness codex): it exited without launching the agent (smoke-test death, ward#222/#264/#595), so it did no work and the launch intent it took is retracted. Nothing is running on this issue. It needs re-dispatch. A ward agent director re-queues it automatically. A manual ward agent retry no longer needs --override-reservation.

— Codex, via ward agent

<!-- ward-agent-reservation-released --> <!-- ward-needs-redispatch --> WARDED_WORKFLOW: reservation-released <details><summary>release details</summary> Run never started. `ward container reap` released container `engineer-codex-ward-1170` (`--harness codex`): it exited without launching the agent (smoke-test death, ward#222/#264/#595), so it did no work and the launch intent it took is retracted. Nothing is running on this issue. It needs re-dispatch. A `ward agent director` re-queues it automatically. A manual `ward agent` retry no longer needs `--override-reservation`. </details> <!-- ward-agent-signature --> — Codex, via `ward agent`
Author
Member

WARDED_WORKFLOW: reservation-held

reservation details

Holder: launch intent for container engineer-codex-ward-1170 on host kais-macbook-pro-2.local.

Accepted by ward agent --harness codex (reserved 2026-07-14T11:17:47Z). Concurrent ward agent runs are blocked until this intent becomes visible or the intent is released. The stale-intent fallback is still TTL-bounded (3h TTL). --override-reservation overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh. That is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

run seed context — what this run is carrying (ward#609)
  • Resolved: coilyco-flight-deck/ward#1170 · branch issue-1170 · harness codex · workflow pull-request-and-merge
  • Run: engineer-codex-ward-1170 · ward v0.679.0 · dispatched 2026-07-14T11:17:43Z
  • Comment thread: 4 included in the pre-flight read, 0 stripped (ward's own automated comments).

Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.679.0).

— Codex, via ward agent

<!-- ward-agent-reservation --> WARDED_WORKFLOW: reservation-held <details><summary>reservation details</summary> Holder: launch intent for container `engineer-codex-ward-1170` on host `kais-macbook-pro-2.local`. Accepted by `ward agent --harness codex` (reserved 2026-07-14T11:17:47Z). Concurrent `ward agent` runs are blocked until this intent becomes visible or the intent is released. The stale-intent fallback is still TTL-bounded (3h TTL). `--override-reservation` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh**. That is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <details><summary>run seed context — what this run is carrying (ward#609)</summary> - **Resolved:** `coilyco-flight-deck/ward#1170` · branch `issue-1170` · harness `codex` · workflow `pull-request-and-merge` - **Run:** `engineer-codex-ward-1170` · ward `v0.679.0` · dispatched `2026-07-14T11:17:43Z` - **Comment thread:** 4 included in the pre-flight read, 0 stripped (ward's own automated comments). - included: @coilyco-ops (2026-07-13T09:23:05Z), @coilyco-ops (2026-07-14T10:03:53Z), @coilyco-ops (2026-07-14T10:07:57Z), @coilyco-ops (2026-07-14T10:22:21Z) Static container doctrine and seed boilerplate are identical every run and omitted here (they ride ward v0.679.0). </details> </details> <!-- ward-agent-signature --> — Codex, via `ward agent`
Author
Member

WARDED_WORKFLOW: #1279

details

workflow: pull-request-and-merge; review summary: review gate skipped by ~/.ward/config.yaml default

felt: mechanical.
confidence: high.
surprises: the closed-unmerged recovery path needed a split helper and a docs split for the size cap.
follow-ups: watch the merge lane until done.

WARDED_WORKFLOW: https://forgejo.coilysiren.me/coilyco-flight-deck/ward/pulls/1279 <details><summary>details</summary> workflow: pull-request-and-merge; review summary: review gate skipped by ~/.ward/config.yaml default felt: mechanical. confidence: high. surprises: the closed-unmerged recovery path needed a split helper and a docs split for the size cap. follow-ups: watch the merge lane until done. </details>
Author
Member

WARDED_WORKFLOW: qa-done

qa details

verdict: pass
reviewed_sha: 9b53386b97
reviewer_family: internal
workflow: pull-request-and-merge
issue_ref: coilyco-flight-deck/ward#1170
pr_ref: coilyco-flight-deck/ward#1279
reason: checks are green and the merged-state postcondition is wired

evidence:

  • ward exec test passed
  • pre-commit run --all-files passed

run_identity: ward-issue-1170

WARDED_WORKFLOW: qa-done ✅ <details><summary>qa details</summary> verdict: pass reviewed_sha: 9b53386b9790fa970a145e16d26420a9b2142152 reviewer_family: internal workflow: pull-request-and-merge issue_ref: coilyco-flight-deck/ward#1170 pr_ref: coilyco-flight-deck/ward#1279 reason: checks are green and the merged-state postcondition is wired evidence: - `ward exec test` passed - `pre-commit run --all-files` passed run_identity: ward-issue-1170 </details>
Author
Member

WARDED_WORKFLOW: done

details

workflow: pull-request-and-merge; review summary: review gate skipped by ~/.ward/config.yaml default

checked head sha: 9b53386b97
status context: test=success
status state: success

merged PR #1279 to main after the merge gate passed.

WARDED_WORKFLOW: done ✅ <details><summary>details</summary> workflow: pull-request-and-merge; review summary: review gate skipped by ~/.ward/config.yaml default checked head sha: 9b53386b9790fa970a145e16d26420a9b2142152 status context: test=success status state: success merged PR #1279 to main after the merge gate passed. </details>
Sign in to join this conversation.
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
coilyco-flight-deck/ward#1170
No description provided.