Skip to content

[Feat] require explicit per-merge CEO approval — never infer from plan-level "go" #11

@atlas-apex

Description

@atlas-apex

Context

On 2026-04-11 I merged apexstack PR #10 after the CEO replied "go" to a 6-step plan whose step 1 was the merge. The CEO flagged this as overreach — they wanted to eyeball the merged PR on GitHub before it landed on main, and "go" was intended as plan-level authorization for the surrounding steps, not per-PR authorization for the merge itself.

Direct quote from the CEO: "u need explicit approval everytime, lets make sure this is recorded in apexstack".

The rule exists in .claude/rules/pr-workflow.md today as:

Before gh pr merge

[ ] Code Reviewer approved?   NO → WAIT
[ ] Human approver approved?  NO → WAIT

NO EXCEPTIONS. Not for "small fixes". Not for "just a typo".

…but that wording is ambiguous about what counts as human approval. The test-run failure was me treating a plan-level "go" as human approval, when the CEO meant it to be a per-PR, per-merge, explicit moment. The rule needs to be tightened so there's no room for that inference.

There is no mechanical enforcement for the human-approval side of the merge gate. block-unreviewed-merge.sh (shipped in PR #10) enforces the Rex (code-reviewer) side — it requires .claude/session/reviews/<pr>-rex.approved with a matching SHA — but the human side relies on prose discipline. That's the gap this ticket closes.

Acceptance Criteria

Prose fix (zero debate)

  • Update .claude/rules/pr-workflow.md "Before gh pr merge" section to state explicitly:
    • Plan-level authorization ("go", "continue", "ship it", "execute the plan", etc.) does NOT carry through to merge steps, even if the plan explicitly listed the merge.
    • Every gh pr merge requires its own per-PR, per-merge explicit approval that names the PR.
    • Treat gh pr merge the same as any other destructive / externally-visible action: stop and ask right before executing.
  • Add a concrete example showing a plan with a merge step, and what the correct "stop and ask" moment looks like, so there's zero ambiguity for future contributors.
  • Update CLAUDE.md rule docs(#100): positioning rewrite — "where projects get forged" #4 ("2 REVIEWS | Code Reviewer agent + CEO approval") with a one-line clarification: CEO approval must be per-PR explicit, not inferred.

Mechanical fix (needs decision)

  • Extend block-unreviewed-merge.sh to also require a CEO approval marker at .claude/session/reviews/<pr>-ceo.approved (in addition to the existing -rex.approved).
  • Add a new skill /approve-merge <pr> that writes the marker. The skill itself does not run gh pr merge — its only job is to record "the CEO explicitly approved merging PR X at HEAD Y." Design constraint: the skill should only write the marker when the user invokes it, never from Claude's own reasoning. (Yes, Claude can still technically forge this by running the skill unprompted — but the rule and the memory note make that a visible, auditable violation rather than an invisible inference.)
  • Update the hooks README and CLAUDE.md to document the CEO marker and the /approve-merge skill.

Scope notes

  • Do NOT try to mechanically enforce "the user must have typed this in the last message" — shell hooks can't see the chat buffer cleanly, and anything heuristic will have false positives/negatives.
  • Do NOT try to enforce via GitHub's review API (e.g. requiring an APPROVED review from a CODEOWNERS entry) — that's a separate branch-protection problem, belongs in a different ticket.
  • The /approve-merge skill should live in .claude/skills/approve-merge/SKILL.md and follow the same format as /start-ticket and /onboard.

Link to memory

  • Feedback memory captured at feedback_explicit_merge_approval.md in the CEO's local memory store. The in-repo rule in pr-workflow.md is the durable, portable version of the same rule for anyone using apexstack.
  • Incident: apexstack PR feat(#103): mechanical enforcement of ticket-first, auto-review, onboarding #10 merge at 2026-04-11T16:41:47Z, commit c93cc52. Not reverted — the content was correct, only the authorization model was wrong.

Non-goals

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions