Skip to content

fix(merge-pr): count the merge owner's approval as the review - #1067

Merged
tbhb-agent merged 2 commits into
mainfrom
feat/merge-owner-satisfies-reviewer-20261101
Nov 2, 2026
Merged

tbhb-agent merged 2 commits into
mainfrom
feat/merge-owner-satisfies-reviewer-20261101

Conversation

@tbhb-agent

@tbhb-agent tbhb-agent commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The unattended merge gate accepts an APPROVED review from the merge owner on the exact current head when the required reviewer hasn't approved. The merge report prints the approving login and head. The shell allow list also gains an exact-path exemption list that only its changed-file check honors, and it lists merge-agent.sh.

Why

An unattended merge stopped at WAITING_REVIEW whenever only the operator had approved, so a pull request with the strongest review available couldn't merge. The operator's exact-head approval now satisfies the reviewer check. Required checks, the owner-review label, write authority and a clean mergeable state still apply, and a later push still invalidates the approval.

Editing the script trips the changed-file shell check while the shell migration is open, so the exemption keeps that check from blocking merge-agent.sh. Explicit path checks still report every finding for it. Remove the entry when the Go port in #864 replaces the script.

Verification

The commands below ran on the head of this pull request. Criteria are the acceptance criteria of #1053.

Criterion Command Result
AC1, AC2 sed -n 118,134p packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh The merge owner's exact-head approval is the fallback when the required reviewer's check fails, and the script prints reviewer satisfied by merge_owner: <login> approved <head>
AC3 sed -n 124,131p packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh Both approval calls take the same $head, and any other outcome stops with WAITING_REVIEW
AC4 git diff origin/main...HEAD -- packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh The diff touches only the approval block, so the check, label, identity and merge-state gates read the same as before
AC5 mise run check-generated-skills passes, and the skill sources, migration note and generated copies all say that the reviewer gate accepts merge_owner
Allow-list exemption mise run test-go passes, including the changed-file exemption test
Shell lint mise run lint-shell passes

The //nolint directives at cmd/shell-allowlist/changed_test.go:4 and cmd/shell-allowlist/run.go:53 already exist on main. git diff origin/main...HEAD -- cmd/shell-allowlist | grep nolint matches nothing in the added lines, so this change adds none.

Risk

Compatibility: none

Loosens: yes

This loosens the merge gate: an exact-head approval by merge_owner now counts as the required_reviewer approval. An unset or wrong merge_owner login makes the fallback never match, and the merge still stops at WAITING_REVIEW. A person must hold merge_owner, because an App could otherwise approve its own change. Revert the commit to restore the previous gate and remove the exemption.

Related

Closes #1053. Part of #864.

An unattended merge required an approval from the configured required
reviewer and stopped when only the operator had approved. That left a
pull request stuck even with the strongest review it could get, because
no second approval was coming.

Now the gate accepts an APPROVED review from the merge owner on the
exact current head when the required reviewer hasn't approved, and the
merge report names the approving login and head. Required checks, the
owner-review label, write authority, and a clean mergeable state still
apply. A later push still invalidates the approval.

Editing that script trips the changed-file shell check while the shell
migration is open, so the shell allow list gains an exact-path
exemption list that only the changed-file check honors. Its single
entry is this script, and explicit path checks still report every
finding for it. Remove the entry when the Go port replaces the script.

Assisted-by: codex:gpt-6.1-sol
Signed-off-by: Tony Burns <tony@tonyburns.net>
@tbhb-agent tbhb-agent added change:fix PR fixes a defect (Conventional Commit type fix) provenance:agent-assisted PR contains commits authored with AI assistance (see Assisted-by trailers) labels Nov 2, 2026
@tbhb-agent
tbhb-agent requested a review from tbhb as a code owner November 2, 2026 13:47

@tbhb-dev-reviews tbhb-dev-reviews Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed head: e47d740
Review: CHANGES REQUESTED
Loosens: yes
Owner-tier: yes; five changed merge-agent.sh files match both owner_paths and constitutional_paths. Exact-head owner approval is required; an App review alone cannot authorize this head.

Live AC Met on reviewed head Evidence
#1053 AC1 Yes packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh:124-131 accepts the configured owner's exact-head approval as fallback.
#1053 AC2 Yes packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh:128 prints the required login and head.
#1053 AC3 Yes packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh:124-130 uses the same current head for both logins; internal/mergepolicy/approval.go:51 rejects stale or unrelated approvals.
#1053 AC4 Yes packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh:115-116,133-178 retains mergeability, identity, label, policy and required-check gates.
#1053 AC5 No packages/agents/source/skills/agent-merge-pr/SKILL.codex.md:20 still requires the configured reviewer while :23 accepts the owner instead; docs/agent-skills-migration.md:139 contradicts :141 in the same way.

#1053 has no comments or Refinement verdict. #864 has a READY verdict (round 3); all twelve Go migration ACs remain for that later slice. This PR does not implement the Go migration and correctly uses a non-closing reference for #864. The present Closes #1053 is premature while AC5 remains unmet. The PR Verification also needs explicit AC1-AC5 IDs with their evidence rather than only a general command table.

Findings:

  • P2: Make the operational instructions agree with the fallback. packages/agents/source/skills/agent-merge-pr/SKILL.codex.md:20 and packages/agents/source/skills/agent-merge-pr/SKILL.claude.md:26 still demand an exact-head approval from required_reviewer; their new authority paragraphs permit merge_owner instead. docs/agent-skills-migration.md:139 likewise states the original unconditional requirement. Update those statements to allow either qualifying approval and propagate the generated copies. Otherwise AC5 fails and an agent following step 4 can repeat the refusal this change fixes.
  • P2: The requested zero-nolint condition is not met. cmd/shell-allowlist/changed_test.go:4 and cmd/shell-allowlist/run.go:53 retain existing nolint directives. Neither directive was introduced by this patch. No t.Skip or weakened assertion was added.

The required-reviewer fallback is an intentional gate relaxation, and .devtools/shell-allowlist.toml:107 additionally skips the exact source script in changed-file mode. No new loosening-marker suppression was added. The exemption is authorized by #1053's scope; cmd/shell-allowlist/changed_test.go:76-90 checks the neighboring-path and explicit-path boundaries. No additional unneeded guard, option, abstraction or layer was found.

Size: scc 4.1.0 measured 43 changed code lines and 9 nonblank prose lines (additions plus deletions; the gate's vendor, generated-copy, token and lock exclusions applied), within 800/2000. Raw churn is 197 lines. No size P1.

Checks: all 23 observed contexts were COMPLETED SUCCESS. Coverage: SUCCESS. CodeQL: SUCCESS, including Go and Actions analysis. GitHub reports MERGEABLE, BLOCKED; ready, not draft. No exact-head approval was present in the reviews read. Private-list leak scans passed for all 27 changed files, one invocation per file. Mechanical gates were not rerun.

Changed paths from git diff --name-status origin/main...HEAD:

Status / path Owner-tier Constitutional
M .agents/skills/agent-merge-pr/SKILL.md no no
M .agents/skills/agent-merge-pr/tokens.json no no
M .agents/skills/merge-pr/scripts/merge-agent.sh yes yes
M .agents/skills/merge-pr/tokens.json no no
M .claude/skills/agent-merge-pr/SKILL.md no no
M .claude/skills/agent-merge-pr/tokens.json no no
M .claude/skills/merge-pr/scripts/merge-agent.sh yes yes
M .claude/skills/merge-pr/tokens.json no no
M .devtools/shell-allowlist.toml no no
M AGENTS.md no no
M apm.lock.yaml no no
M cmd/shell-allowlist/changed_test.go no no
M cmd/shell-allowlist/run.go no no
M docs/agent-skills-migration.md no no
M docs/shell-allowlist.md no no
M internal/shell/shell.go no no
M packages/agents/claude/.apm/skills/agent-merge-pr/SKILL.md no no
M packages/agents/claude/.apm/skills/agent-merge-pr/tokens.json no no
M packages/agents/claude/.apm/skills/merge-pr/scripts/merge-agent.sh yes yes
M packages/agents/claude/.apm/skills/merge-pr/tokens.json no no
M packages/agents/codex/.apm/skills/agent-merge-pr/SKILL.md no no
M packages/agents/codex/.apm/skills/agent-merge-pr/tokens.json no no
M packages/agents/codex/.apm/skills/merge-pr/scripts/merge-agent.sh yes yes
M packages/agents/codex/.apm/skills/merge-pr/tokens.json no no
M packages/agents/source/skills/agent-merge-pr/SKILL.claude.md no no
M packages/agents/source/skills/agent-merge-pr/SKILL.codex.md no no
M packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh yes yes

Policy patterns at .agents/agent-skills.conf:9 and :5:

owner_paths = ^(\.agents/(agent-skills\.conf|review-instructions\.md)$|\.github/rulesets/|\.claude/settings\.json$|packages/agents/source/(lib/(lib|agent)\.sh$|skills/[^/]+/(shared|claude|codex)/scripts/guard[^/]*\.sh$|skills/merge-pr/(shared|claude|codex)/scripts/(merge-agent\.sh|merge-policy\.py)$)|(packages/agents/(claude|codex)/\.apm/skills|\.(claude|agents)/skills)/([^/]+/scripts/(guard[^/]*\.sh|(lib|agent)\.sh)$|(merge-pr|agent-merge-pr)/scripts/(merge-agent\.sh|merge-policy\.py)$))

constitutional_paths = ^(\.agents/(agent-skills\.conf|review-instructions\.md)$|\.github/(rulesets/|CODEOWNERS$)|\.claude/settings\.json$|packages/agents/source/(lib|skills/merge-pr/(shared|claude|codex)/scripts)/(merge-policy\.py|merge-agent\.sh|signing\.sh|lib\.sh)$|(packages/agents/[^/]+/\.apm/skills|\.(claude|agents)/skills)/[^/]+/scripts/(merge-policy\.py|merge-agent\.sh|signing\.sh|lib\.sh)$)

Owner-path-only matches: none. Every matching changed path is constitutional and requires owner approval of the reviewed head.

Round: 1 of 3

The unattended merge script already accepts an exact-head approval
from the merge owner when the required reviewer hasn't approved. The
agent-merge-pr step 4 text and the migration note still said the
merge needs the required reviewer's approval. An agent following step
4 would stop at the refusal this fallback exists to avoid.

Both statements describe the fallback, and the generated skill copies
carry it.

Assisted-by: claude:claude-sonnet-5-5
Signed-off-by: Tony Burns <tony@tonyburns.net>

@tbhb-dev-reviews tbhb-dev-reviews Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed head: 176af34
Review: APPROVED
Loosens: yes
Owner-tier: yes. Five changed merge-agent.sh paths are constitutional; exact-head owner approval is still required after this approval. The review App alone does not authorize merging this head.

Live AC Met on this exact head Evidence
#1053 AC1 Yes packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh:124 accepts merge_owner approval when required_reviewer has not approved.
#1053 AC2 Yes packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh:128 prints the approving login and head.
#1053 AC3 Yes packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh:127 uses the same current head; :130 retains WAITING_REVIEW. internal/mergepolicy/approval.go:51 rejects stale or unrelated approvals.
#1053 AC4 Yes packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh:115 and :133 retain identity, readiness, label, policy and required-check behavior; the diff changes only reviewer fallback.
#1053 AC5 Yes packages/agents/source/skills/agent-merge-pr/SKILL.codex.md:20 and packages/agents/source/skills/agent-merge-pr/SKILL.claude.md:26 now allow the fallback, matching their authority paragraphs and generated copies; docs/agent-skills-migration.md:139 and AGENTS.md:72 agree.

Findings: none. Round 1's AC5 contradiction is resolved. Both existing nolint directives are present on origin/main (cmd/shell-allowlist/changed_test.go:4 and cmd/shell-allowlist/run.go:53); this diff adds zero. No t.Skip or weakened assertion was added. merge-agent.sh is unchanged since round 1.

The PR Verification explicitly covers AC1-AC5 (AC1 and AC2 share one row). Closes #1053 is supported; no #1053 AC is deferred. That issue has no comments or Refinement verdict. #864 has a READY Refinement verdict; its AC1-AC12 remain for the later Go migration, and its non-closing reference is appropriate.

The approval fallback intentionally loosens the gate. .devtools/shell-allowlist.toml:107 also exempts the exact source script only in changed-file mode; cmd/shell-allowlist/run.go:37 enforces that boundary, and cmd/shell-allowlist/changed_test.go:76 checks neighboring and explicit paths. No additional loosening-marker suppression or unnecessary guard, option, abstraction or layer was found. Constitutional and loosened-gate findings remain outside the App-only golden-file waiver at packages/agents/source/skills/merge-pr/shared/scripts/merge-policy.py:576.

Size: scc 4.1.0 measured 43 changed code lines and 15 nonblank prose lines, additions plus deletions, excluding vendor, generated copies, token metadata and the lockfile as in the size gate. Within 800/2000; no size P1. Raw churn: 211 lines.

Checks: all 23 contexts read were COMPLETED SUCCESS. Coverage: SUCCESS. CodeQL: SUCCESS, including Go and Actions analysis. Ready, not draft; MERGEABLE, BLOCKED. Private-list leak scans passed for all 27 changed files, one invocation per file. Mechanical gates were not rerun.

Changed paths against origin/main:

Status / path Owner-tier Constitutional
M .agents/skills/agent-merge-pr/SKILL.md no no
M .agents/skills/agent-merge-pr/tokens.json no no
M .agents/skills/merge-pr/scripts/merge-agent.sh yes yes
M .agents/skills/merge-pr/tokens.json no no
M .claude/skills/agent-merge-pr/SKILL.md no no
M .claude/skills/agent-merge-pr/tokens.json no no
M .claude/skills/merge-pr/scripts/merge-agent.sh yes yes
M .claude/skills/merge-pr/tokens.json no no
M .devtools/shell-allowlist.toml no no
M AGENTS.md no no
M apm.lock.yaml no no
M cmd/shell-allowlist/changed_test.go no no
M cmd/shell-allowlist/run.go no no
M docs/agent-skills-migration.md no no
M docs/shell-allowlist.md no no
M internal/shell/shell.go no no
M packages/agents/claude/.apm/skills/agent-merge-pr/SKILL.md no no
M packages/agents/claude/.apm/skills/agent-merge-pr/tokens.json no no
M packages/agents/claude/.apm/skills/merge-pr/scripts/merge-agent.sh yes yes
M packages/agents/claude/.apm/skills/merge-pr/tokens.json no no
M packages/agents/codex/.apm/skills/agent-merge-pr/SKILL.md no no
M packages/agents/codex/.apm/skills/agent-merge-pr/tokens.json no no
M packages/agents/codex/.apm/skills/merge-pr/scripts/merge-agent.sh yes yes
M packages/agents/codex/.apm/skills/merge-pr/tokens.json no no
M packages/agents/source/skills/agent-merge-pr/SKILL.claude.md no no
M packages/agents/source/skills/agent-merge-pr/SKILL.codex.md no no
M packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh yes yes

Policy at .agents/agent-skills.conf:9 and :5:

owner_paths = ^(\.agents/(agent-skills\.conf|review-instructions\.md)$|\.github/rulesets/|\.claude/settings\.json$|packages/agents/source/(lib/(lib|agent)\.sh$|skills/[^/]+/(shared|claude|codex)/scripts/guard[^/]*\.sh$|skills/merge-pr/(shared|claude|codex)/scripts/(merge-agent\.sh|merge-policy\.py)$)|(packages/agents/(claude|codex)/\.apm/skills|\.(claude|agents)/skills)/([^/]+/scripts/(guard[^/]*\.sh|(lib|agent)\.sh)$|(merge-pr|agent-merge-pr)/scripts/(merge-agent\.sh|merge-policy\.py)$))

constitutional_paths = ^(\.agents/(agent-skills\.conf|review-instructions\.md)$|\.github/(rulesets/|CODEOWNERS$)|\.claude/settings\.json$|packages/agents/source/(lib|skills/merge-pr/(shared|claude|codex)/scripts)/(merge-policy\.py|merge-agent\.sh|signing\.sh|lib\.sh)$|(packages/agents/[^/]+/\.apm/skills|\.(claude|agents)/skills)/[^/]+/scripts/(merge-policy\.py|merge-agent\.sh|signing\.sh|lib\.sh)$)

Owner-path-only matches: none. Every owner-tier match above is constitutional and requires approval of this exact head by the owner.

Round: 2 of 3

@tbhb-agent tbhb-agent added the merge:owner-review Needs the owner's review before merge label Nov 2, 2026
@tbhb-agent
tbhb-agent merged commit b5a2efd into main Nov 2, 2026
23 checks passed
@tbhb-agent
tbhb-agent deleted the feat/merge-owner-satisfies-reviewer-20261101 branch November 2, 2026 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:fix PR fixes a defect (Conventional Commit type fix) merge:owner-review Needs the owner's review before merge provenance:agent-assisted PR contains commits authored with AI assistance (see Assisted-by trailers)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(merge-pr): let the merge_owner approval satisfy the required_reviewer gate

2 participants