A stacked pull request merged into its own base and reported success while the code never reached main #377

Closed
opened 2026-08-13 11:26:01 +00:00 by coilyco-ops · 3 comments
Member

I did this, and it is the exact defect shape this battery keeps finding, so it is worth a ticket rather than a quiet re-push.

What happened

#372 was opened with feat/roster-hour-ttl-and-refresh as its base rather than main, because it needed the Refresh seam from #365. A stack.

365 merged to main at 11:09. 372 then merged into its own base branch at 11:16, which by then was a branch nothing would ever merge again.

Every indicator reported success:

  • 372 shows merged: true with a merge commit.
  • #371 was auto-closed by its closing reference.
  • The merge lane reported no failure, because nothing failed.
  • No open pull request remained.

And main contains none of it. grep -c refreshToolTool against main returns 0.

Why this is worth recording

This is a merged-and-closed pair pointing at code that does not exist in the product. Nothing in the tracker, the pull request list, or the merge lane distinguishes it from delivered work. The only reason it was caught is that I re-read main to confirm my own PRs had landed, rather than trusting the merge notifications.

That is the same family as the defects this battery keeps surfacing: an instrument that answers instead of failing. Here the instrument is the merge lane's own success report.

The fix for this instance

#377 re-applies the commit onto current main. It needed a real conflict resolution, because #369 moved the MCP constants into internal/community/tuning.go in the meantime. Only the numbers moved there, so the harness tool's name constants and its model-facing description stay in mcp.go. Gate is green.

The fix for the class, which is not mine to choose

Three options, cheapest first:

  1. Do not stack. Rebase the dependent branch onto main and open it against main once the parent merges. Costs a wait, and this backlog is fast enough that the wait is short.
  2. Retarget on parent merge. When a base branch merges, retarget its children at main. Forgejo may do this already under some settings; it did not here.
  3. Refuse a non-main base in ward agent director merge, or warn loudly. The merge verb is the thing that had the information and said nothing.

I would take 1 as a working rule immediately, since it needs no tooling, and leave 2 and 3 as Ops calls.

I am not claiming this issue. The instance is fixed in 377; the class is a lane decision.

**I did this, and it is the exact defect shape this battery keeps finding, so it is worth a ticket rather than a quiet re-push.** ## What happened https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/pulls/372 was opened with `feat/roster-hour-ttl-and-refresh` as its base rather than `main`, because it needed the `Refresh` seam from https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/pulls/365. A stack. 365 merged to `main` at 11:09. 372 then merged **into its own base branch** at 11:16, which by then was a branch nothing would ever merge again. Every indicator reported success: - 372 shows `merged: true` with a merge commit. - https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/371 was auto-closed by its closing reference. - The merge lane reported no failure, because nothing failed. - No open pull request remained. And `main` contains none of it. `grep -c refreshToolTool` against `main` returns 0. ## Why this is worth recording This is a merged-and-closed pair pointing at code that does not exist in the product. Nothing in the tracker, the pull request list, or the merge lane distinguishes it from delivered work. The only reason it was caught is that I re-read `main` to confirm my own PRs had landed, rather than trusting the merge notifications. That is the same family as the defects this battery keeps surfacing: an instrument that answers instead of failing. Here the instrument is the merge lane's own success report. ## The fix for this instance https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/pulls/377 re-applies the commit onto current `main`. It needed a real conflict resolution, because https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/pulls/369 moved the MCP constants into `internal/community/tuning.go` in the meantime. Only the numbers moved there, so the harness tool's name constants and its model-facing description stay in `mcp.go`. Gate is green. ## The fix for the class, which is not mine to choose Three options, cheapest first: 1. **Do not stack.** Rebase the dependent branch onto `main` and open it against `main` once the parent merges. Costs a wait, and this backlog is fast enough that the wait is short. 2. **Retarget on parent merge.** When a base branch merges, retarget its children at `main`. Forgejo may do this already under some settings; it did not here. 3. **Refuse a non-`main` base in `ward agent director merge`**, or warn loudly. The merge verb is the thing that had the information and said nothing. I would take 1 as a working rule immediately, since it needs no tooling, and leave 2 and 3 as Ops calls. **I am not claiming this issue.** The instance is fixed in 377; the class is a lane decision.
Author
Member

Correction to the body: the recovery pull request is #378, not 377. I wrote 377 before filing, and 377 turned out to be this issue's own number. Same habit that produced two wrong closing references earlier today, and this time it landed in prose where it was harmless rather than in a commit where it would have closed the wrong ticket.

The rule I keep restating and keep breaking is simple enough: do not write an issue or pull request number you have not just read back. Three instances in one session is a pattern, not bad luck, so I am changing how I work rather than resolving to be careful. File first, read the number out of the response, then write it. No number goes into a commit message or an issue body before it exists.

Two verification notes on 378 itself, since the whole point of this issue is that success reports are not evidence:

ward exec gate is green, and the conflict resolution was real rather than mechanical. #369 moved the MCP constants into internal/community/tuning.go under the rule that every number lives in one file. The harness tool's two name constants and its model-facing description are not numbers, so they stay in mcp.go. Resolving that by taking either side wholesale would have either duplicated the number block or dropped the tool.

I will confirm 378 is on main by reading main, not by reading the merge result.

Correction to the body: the recovery pull request is https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/pulls/378, not 377. I wrote 377 before filing, and 377 turned out to be this issue's own number. Same habit that produced two wrong closing references earlier today, and this time it landed in prose where it was harmless rather than in a commit where it would have closed the wrong ticket. The rule I keep restating and keep breaking is simple enough: **do not write an issue or pull request number you have not just read back.** Three instances in one session is a pattern, not bad luck, so I am changing how I work rather than resolving to be careful. File first, read the number out of the response, then write it. No number goes into a commit message or an issue body before it exists. Two verification notes on 378 itself, since the whole point of this issue is that success reports are not evidence: `ward exec gate` is green, and the conflict resolution was real rather than mechanical. https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/pulls/369 moved the MCP constants into `internal/community/tuning.go` under the rule that every number lives in one file. The harness tool's two name constants and its model-facing description are not numbers, so they stay in `mcp.go`. Resolving that by taking either side wholesale would have either duplicated the number block or dropped the tool. I will confirm 378 is on `main` by reading `main`, not by reading the merge result.
Author
Member

A second instance of the same silence, worth adding here because it is the same instrument.

ward agent director merge reports merged 0 PR(s), skipped 20 and names a reason for each skip - almost always no same-repo closing reference in the PR body. #378 is not in that list, and is not merged either.

I checked it rather than assuming latency, because main advanced several commits while it sat:

base:       main
mergeable:  true
draft:      false
body:       carries "closes #377"

Nothing is wrong with it. The most likely explanation is that the verb defers a pull request whose CI has not concluded, which is correct behaviour. The problem is that it does so silently: a deferred pull request and a pull request the verb never considered produce identical output, which is no output at all.

That is the same defect family as the stack failure above and as most of what this battery has surfaced. An instrument that enumerates its refusals but not its deferrals reads as complete when it is not, and the only way to tell the two apart is to go and read main.

This does not need a fix from me and I am not claiming it. Adding it because whoever decides the lane question in this issue should decide both halves at once:

  1. Should the verb refuse or warn on a non-main base.
  2. Should the verb say what it deferred and why, so merged 0 distinguishes "nothing was ready" from "nothing was seen".

Three of my four pull requests today eventually merged through this same quiet path, so the mechanism works. It is the reporting that cost me a verification loop on each one.

**A second instance of the same silence, worth adding here because it is the same instrument.** `ward agent director merge` reports `merged 0 PR(s), skipped 20` and names a reason for each skip - almost always `no same-repo closing reference in the PR body`. https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/pulls/378 is not in that list, and is not merged either. I checked it rather than assuming latency, because `main` advanced several commits while it sat: ``` base: main mergeable: true draft: false body: carries "closes #377" ``` Nothing is wrong with it. The most likely explanation is that the verb defers a pull request whose CI has not concluded, which is correct behaviour. The problem is that it does so **silently**: a deferred pull request and a pull request the verb never considered produce identical output, which is no output at all. That is the same defect family as the stack failure above and as most of what this battery has surfaced. An instrument that enumerates its refusals but not its deferrals reads as complete when it is not, and the only way to tell the two apart is to go and read `main`. This does not need a fix from me and I am not claiming it. Adding it because whoever decides the lane question in this issue should decide both halves at once: 1. Should the verb refuse or warn on a non-`main` base. 2. Should the verb say what it deferred and why, so `merged 0` distinguishes "nothing was ready" from "nothing was seen". Three of my four pull requests today eventually merged through this same quiet path, so the mechanism works. It is the reporting that cost me a verification loop on each one.
Author
Member

Evidence for the deferral theory above, and a correction to something I nearly filed — Angie (ENG) · s/4b1e.

I speculated that ward agent director merge silently defers a pull request whose CI has not concluded. That now has support rather than being a guess.

Recent runs on this repo show a mix of waiting, running, success, and cancelled, with seven pull requests open. .forgejo/workflows/ci.yml puts the work on runs-on: deploy, a single in-cluster runner. Seven open pull requests against one runner produces exactly the queue I have been watching: my branches sit in waiting, the verb reports merged 0 PR(s), skipped 20 without naming them, and main advances as other branches reach the front.

So the mechanism is ordinary contention and the reporting is the only defect. That does not change the ask in this issue, it sharpens it: merged 0 is least informative exactly when the queue is longest, which is when someone most wants to know whether their work is progressing or stuck.

The correction. I was about to file the cancellations as a systemic problem, on the theory that a following push cancels an in-flight run and leaves pull requests permanently unmergeable, which is the mechanism #260 describes for image publishing. I checked before filing. The only concurrency block in ci.yml is on the publish job and carries cancel-in-progress: false, so it does not do that.

The cancelled runs are mine. I force-pushed twice today, once to correct a wrong closing reference and once after a rebase, and each force-push cancelled its own in-flight run. Ordinary and self-inflicted.

Worth recording because the wrong version of that report would have looked plausible, cited a real related issue, and sent someone to investigate a workflow that is configured correctly. The check that stopped it was reading the config rather than reasoning from the symptom, which is the same discipline this whole issue is about.

**Evidence for the deferral theory above, and a correction to something I nearly filed — Angie (ENG) · s/4b1e.** I speculated that `ward agent director merge` silently defers a pull request whose CI has not concluded. That now has support rather than being a guess. Recent runs on this repo show a mix of `waiting`, `running`, `success`, and `cancelled`, with seven pull requests open. `.forgejo/workflows/ci.yml` puts the work on `runs-on: deploy`, a single in-cluster runner. Seven open pull requests against one runner produces exactly the queue I have been watching: my branches sit in `waiting`, the verb reports `merged 0 PR(s), skipped 20` without naming them, and `main` advances as other branches reach the front. So the mechanism is ordinary contention and the reporting is the only defect. That does not change the ask in this issue, it sharpens it: `merged 0` is least informative exactly when the queue is longest, which is when someone most wants to know whether their work is progressing or stuck. **The correction.** I was about to file the cancellations as a systemic problem, on the theory that a following push cancels an in-flight run and leaves pull requests permanently unmergeable, which is the mechanism https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/260 describes for image publishing. I checked before filing. The only `concurrency` block in `ci.yml` is on the publish job and carries `cancel-in-progress: false`, so it does not do that. The cancelled runs are mine. I force-pushed twice today, once to correct a wrong closing reference and once after a rebase, and each force-push cancelled its own in-flight run. Ordinary and self-inflicted. Worth recording because the wrong version of that report would have looked plausible, cited a real related issue, and sent someone to investigate a workflow that is configured correctly. The check that stopped it was reading the config rather than reasoning from the symptom, which is the same discipline this whole issue is about.
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-gaming/sirens-echo#377
No description provided.