Skip to content

🐛 fix(automerge): an owner's approval carries over the hive's base merge when main only moved the PR's hunks - #17374

Merged
hivecommons-hive[bot] merged 4 commits into
hivecommons:v6from
Danathar:fix/v6-approval-carry
Nov 9, 2026
Merged

hivecommons-hive[bot] merged 4 commits into
hivecommons:v6from
Danathar:fix/v6-approval-carry

Conversation

@Danathar

@Danathar Danathar commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

Summary

When the hive merges main into a PR that edits workflow files, an owner's earlier approval is supposed to carry over as long as the PR's diff is unchanged. The check compared each file's resulting blob, so if main had also edited a file the PR touches (even far from the PR's lines), the blob changed, the approval did not carry, and the PR sat at stage/085-needs-decision until someone approved the exact same change again. This PR lets that case carry when every hunk is byte-for-byte the same and only its line numbers moved. Files under .github/ still have to match exactly.

What went wrong

  • projectbluefin/utah-packages#924: hanthor's /hive decision approve released the fence on 1417928 (audit marker posted 11-07 17:06Z). The only commit after that is the hive's own verified merge of main (11e168e, 17:06:16Z). The PR went back to 085 with contributor-lane:workflow-path and stayed there until Danathar approved again on 11-09.
  • Why: in the three-dot compare, four of the five files are identical. docs/skills/upstream-version-bumps.md went from blob 3604514 to 4da6d8a because main edited that doc above the PR's hunks. Its patch is the same except the hunk headers (@@ -145,32 +145,82 @@ became @@ -160,32 +160,82 @@). sameChangedFiles required equal blobs, so HeadIsBaseRefreshOf said no and nothing carried.
  • The carry from 🐛 fix: workflow-path fence approval survives the hive's own base refresh #16858 is already on v6 and in the live build. This is a gap in the shared diff-equality check it relies on, not a missing port.

What changes

  • sameChangedFiles (the check behind HeadIsBaseRefreshOf and BaseMergeParent) now accepts a different blob for one file when all of these hold:
    • it is a plain modification outside any .github/ directory;
    • GitHub returned a patch for both sides;
    • the two patches are equal once each hunk header's start line numbers are removed, so context lines, added and removed lines, hunk lengths and section headings must all match.
  • Anything else is still a different diff: workflow or action files whose blob changed, a changed context line, a missing patch, a rename. The line counts are still compared too.
  • The docs (hive-merge.md) and code comments now describe the new rule.

Why this is safe

The merge has to be a GitHub-verified App merge whose other parent is the merge-base with the base branch (unchanged). Anything added in that merge that is not on the base would appear in the PR's diff against that merge-base, so it would show up as a changed or extra hunk. The relaxed rule only accepts edits that main already made, away from the approved lines. Workflow and action files keep the strict rule, because a base edit elsewhere in such a file (its triggers or permissions) can change what the approved lines do.

Files

  • src/pkg/github/pr_decision_base_refresh.go: sameHunksAtShiftedLines, patchWithoutHunkStarts, underDotGitHub
  • src/pkg/github/automerge/automerge_contributor_workflow_approval.go: comment only
  • src/docs/hive-merge.md, changelog.d/fixed-approval-carry-shifted-hunks.md

Tests

  • TestSameChangedFilesToleratesShiftedHunksOutsideDotGitHub: the feat: add OpenAPI spec and Redoc API docs #924 case compares equal. Eight near-misses still compare different: changed context, changed hunk length, changed section heading, omitted patch, rename, a workflow file, a nested .github/actions file, and a workflow file with an identical patch but a different blob.
  • TestContributorLaneWorkflowFenceApprovalCarriesOverHiveBaseRefresh has three new rows. The fence carries across an App base merge that shifted a doc's hunks. It re-arms when the doc hunk's context changed (the fix(canary): carry a fork's Canary proof across clean merges of main that change no pipeline path projectbluefin/utah-packages#900 shape), and it re-arms when the workflow file itself drifted.
  • On the v6 tip, the first row of each fails, because blob inequality alone returned false. The other rows pass both before and after.

Refs #16858

🤖 Generated with Claude Code

https://claude.ai/code/session_01BQYnPGFJx81c8JXR3o1sg1

…ed the PR's hunks

The workflow-path fence (and the operator-decision carry, the review-head
binding and the base-preparation proof, which share the same check) carried
an approval across the hive's own merge of the base branch only when every
file in the PR's three-dot diff kept the same resulting blob. When main had
also edited a file the PR changes, away from the PR's lines, the blob
changed even though every hunk was byte-for-byte the same at shifted line
numbers, so the fence re-armed and the PR sat at stage/085 until someone
approved the identical change again (projectbluefin/utah-packages#924:
hanthor approved 1417928, the hive merged main in as 11e168e, and main had
edited docs/skills/upstream-version-bumps.md 15 lines above the PR's hunk).

sameChangedFiles now accepts a different blob for a plain modification
outside any .github/ directory when GitHub returned a patch for both sides
and the patches are equal once each hunk header's two start lines are
dropped (context, added and removed lines, hunk lengths and section
headings must all match). Workflow and action files still have to read
exactly as approved, and a merge that changed the PR's hunks or their
context still needs a new approval.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BQYnPGFJx81c8JXR3o1sg1
Signed-off-by: Danathar <Danathar@users.noreply.github.com>
@kubestellar-prow kubestellar-prow Bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Nov 9, 2026
@kubestellar-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign kellyaa for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubestellar-prow kubestellar-prow Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Nov 9, 2026

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decision needed: @clubanderson should decide whether the workflow-fence approval may carry over an App base merge when a non-.github file the PR edits was also changed by main outside the PR's hunks (hunks byte-identical, only line numbers moved), or whether the carry must keep requiring identical resulting blobs for every file.

Code read at 3abd102 against v6; no defect found. This is a merge/trust policy loosening, so I am routing rather than judging it alone.

What the code does (verified):

  • sameChangedFiles (pr_decision_base_refresh.go:289-299) now tolerates a blob mismatch only via sameHunksAtShiftedLines, after name/status/rename and additions/deletions/changes already matched. The continue skips the raw-patch equality, but sameHunksAtShiftedLines has already required both patches present, non-empty, and equal after normalising hunk headers (:309-324), so nothing is skipped.
  • patchWithoutHunkStarts (:335-359) only rewrites lines prefixed @@; unified-diff body lines start with , +, - or \, so content is never touched. A header that fails the regex, or a patch with no hunks (GitHub omits patches for large files), returns false — conservative.
  • .github/ at any depth is excluded (underDotGitHub, :329-332), covered by the "nested action file" test case.

The policy question: the carry now accepts a head whose resulting content in a non-.github/ file differs from what the approver saw, as long as the PR's own hunks and their three-line context are unchanged. The extra content came from main, so it was already trusted on the base branch, which is the argument for carrying. The counter-argument is that files outside .github/ (scripts a workflow runs, Dockerfiles, Makefiles) can also change what the approved lines do. The PR body argues for the first reading; the .github/ carve-out is the only line drawn. Whether that line is the right one is a maintainer call, not a code call.

I have not approved or changed anything.

— hive: agent=reviewer-2 backend=copilot model=claude-fable-5.1 copilot=1.0.88

@github-actions

github-actions Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

Open PR overlap check

Base branch: v6

Conflicts are checked with every PR merged onto the current tip of v6, so a PR that only needs a rebase is not reported as conflicting with its siblings.

No conflicting open PRs or same-file overlaps were found.

@hivecommons-hive

hivecommons-hive Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Needs a decision, not a merge:

  • the reviewer swarm returned requires_human @c1eca1a
  • the review found: the reviewer marked this change high-risk
  • reviewer question: @clubanderson should decide whether an owner's approval (and a recorded /hive decision) may carry over an App base merge when the base also edited a non-.github file the PR changes, provided every PR hunk is byte-identical and only its line numbers moved.
  • the automerge lane will not merge this unattended

Head: verdict requires_human @c1eca1a, 44/53 checks green, 9 pending, DCO yes, soak 0s of 10m0s.

How to release it:

  • After reading the verdict, a trusted merger can submit a GitHub Approve review for the current head and the lane clears the hold on its next pass; /hive approve replies on a PR are acknowledged but not consumed.

Clear the cause and the hive re-evaluates on its next pass; the stage moves to stage/090-ready-for-human-merge, stage/095-pre-merge-soak or the merge queue on its own. Approval does not bypass CI, hold labels or other merge gates.

Updated 2026-11-09 12:20 UTC.

@hivecommons-hive

hivecommons-hive Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Hive status — updated in place; edits do not notify. Full history is in the Activity Monitor.

Review findings from reviewer (folded here because this head already carries its hive review):

The code does what it says; I am routing this because it is a deliberate loosening of the approval-carry gate on the contributor workflow-path fence, which is merge/trust policy and not a reviewer's call.

What I verified (read against v6):

  • sameChangedFiles (src/pkg/github/pr_decision_base_refresh.go) previously required equal blob SHAs per file. Now a blob mismatch falls into sameHunksAtShiftedLines, which still demands: status == modified on both sides, no .github/ path (any depth, including previous_filename), a non-empty patch on both sides, every @@ header parseable, and patch text equal once only the two hunk start numbers are blanked. Additions/deletions/changes counts are still compared before that. So context lines, hunk lengths and the section heading all still have to match — a base edit that reaches into or adjacent to a hunk still re-arms the fence.
  • A content line beginning @@ cannot be mistaken for a header: body lines carry a +/-/space prefix, so strings.HasPrefix(line, "@@") only sees headers. A patch GitHub omitted (large file) returns false, which is the conservative direction.
  • The surrounding gate is untouched: the new head must still be a GitHub-verified merge commit by the App with oldHead as a parent (CommitIsVerifiedAppMerge), so a contributor cannot produce the head that triggers this path.
  • Tests cover shifted hunks carrying, .github/ never carrying, hunk-length and context changes not carrying, and the missing-patch case.

The question for a maintainer: the fence exists so a person re-approves when a workflow-path PR's head changes. This PR argues that a base merge which moves a non-workflow hunk's line numbers is the same approved change; the counter-argument is that a base edit elsewhere in the same file can change what the approved lines do (the PR already accepts that argument for .github/ files and rejects it for everything else). Note the author is an OWNERS approver, so this may just need a second approver's nod rather than a debate.

No defects found. I have not approved or changed anything.

Review findings from reviewer-2 (folded here because this head already carries its hive review):

The implementation does what it says and fails closed on every ambiguous case I could construct; the remaining question is whether the merge-trust gate should be relaxed this way at all, which is a maintainer call.

Verified (src/pkg/github/pr_decision_base_refresh.go, PR head)

  • sameChangedFiles still requires identical filename, status, rename source, additions, deletions and changes before the blob-SHA tolerance is reached, so a base edit that adds or removes lines inside the PR's hunks cannot pass.
  • sameHunksAtShiftedLines requires both entries to be modified, both patches present and non-empty, no path under .github/ (checked on current and previous filename of both sides), and every @@ header to parse. A missing patch (GitHub omits it for large files) returns false, which keeps the fence armed.
  • patchWithoutHunkStarts drops only the two start lines; hunk lengths and the section heading are kept. Unified-diff body lines begin with , +, - or \, never @@, so no content line is rewritten. A base edit inside the 3-line context of a PR hunk changes the patch body and re-arms; a base edit that makes git merge or split hunks changes the lengths and re-arms.
  • The three new fence tests in automerge_contributor_workflow_approval_test.go cover the carry case, the changed-context case and the .github/ exclusion; TestSameChangedFilesToleratesShiftedHunksOutsideDotGitHub covers the helper directly.
  • hive-merge.md and the changelog fragment describe the new allowance and its limits accurately.

Why I am routing rather than confirming

This widens what counts as "the same change the person approved" for both the workflow-fence approval carry and the BaseRefreshChecker decision carry. The widening is bounded to the PR's own hunks and excludes .github/, but a base edit elsewhere in a Go file, a Makefile or a shell script can still change what approved lines do (a renamed helper, a changed variable above the hunk). The base is a protected branch that already passed the hive's gates, and GitHub's own stale-approval dismissal does not trigger on base motion either, so the risk is arguably the same one every approved PR already carries; the PR body argues this too. I would not land a merge-trust relaxation on an agent's say-so, so the one-line question above is for an approver. If the answer is yes, this looks correct to me as written.

Evidence bundle `#1737421f2713` sha256:a5bb67bdf46c337eb95cfcedebc8f96ca4e3d38dff02cd97d0a2a28ed7577d92 — download from the dashboard or `hivectl review evidence #17374`.

@hivecommons-hive hivecommons-hive Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read sameChangedFiles and the two callers (HeadIsBaseRefreshOf at pr_decision_base_refresh.go:174, BaseMergeParent at :207). The relaxation is well-fenced and looks correct to me:

  • The structural guards that make the carry safe are untouched: the new head's merge-base with the base ref must equal the base-side parent (:167), so anything in the merge not already on the base would show up in the three-dot diff as a changed or extra hunk, and patchWithoutHunkStarts would then return a different string.
  • sameHunksAtShiftedLines (:309-324) refuses anything that is not modified, any path under .github/ at any depth, and a nil/empty patch (GitHub omits patches for large and binary files), so those fall back to the strict blob equality. Additions/deletions/changes counts are still compared before the SHA branch (:281).
  • In a unified diff every content line carries a ' ', +, - or \ prefix, so a line beginning @@ can only be a hunk header; the regex rewrite at :342-353 cannot touch content, and an unparseable header or a hunk-less patch is false.
  • Hunk lengths and section headings remain part of the comparison, so a base edit that changes the enclosing heading or hunk extent is still treated as a different diff. The test table covers each of those near-misses.

Author is an OWNERS approver and the PR body states the scope and the .github/ carve-out explicitly, so no open decision from my side.

— hive: agent=reviewer backend=copilot model=claude-fable-5.1 copilot=1.0.88

@hivecommons-hive
hivecommons-hive Bot merged commit dfce7ef into hivecommons:v6 Nov 9, 2026
67 of 68 checks passed
@hivecommons-hive

Copy link
Copy Markdown
Contributor

merged by Hive trusted-author auto-merge: author @Danathar holds role owner and GitHub permission maintain on repo; CI green at 21f2713.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dco-signoff: yes Indicates the PR's author has signed the DCO. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant