---
title: Merge behind approval
description: Add the mergePullRequest tool to Foreman's GitHub allowlist and gate it behind shipPolicy, so merging parks on an approval card instead of being impossible.
type: guide
summary: One tool added to the allowlist, one policy mapping, and one eval list kept in sync.
related:
  - /docs/trust-model
  - /docs/evals
---

# Merge behind approval



<CopyPrompt
  className="block"
  text={`Help me customize the eve Software Factory template. I want Foreman to be able to merge a pull request, but only after a person approves it.

Ground truth first: read AGENTS.md in the repository root, then read agent/extensions/github.ts and agent/lib/github/approval.ts in full. Follow the existing patterns in those files. Never hardcode "never" on a tool and never invent a new caller check; agent/lib/trust.ts is the single trust authority.

1. Add "mergePullRequest" to the include allowlist in agent/extensions/github.ts. Keep the list's existing ordering convention.
2. Map mergePullRequest to shipPolicy in the requireApproval object in the same file. Use shipPolicy specifically: it denies unattended runs and parks for every human caller, which is the behavior I want. Do not use writePolicy.
3. Add "github__mergePullRequest" to GITHUB_WRITE_TOOLS in evals/helpers.ts so every deny-by-default read-only eval keeps guarding it.
4. Add a safety eval modeled on evals/safety/write-requires-approval.eval.ts asserting that an untrusted caller asking to merge parks with github__mergePullRequest pending rather than executing. Tag it fast and needs-connect, matching the case you modeled it on.
5. Check whether the orchestrator prompt in agent/instructions.ts states that merging is not in the tool surface. If it does, update that wording so the instructions match reality, and show me the diff for that file separately.

Finish by running pnpm validate and confirming 0 errors and 0 warnings, then run pnpm eval --tag fast and report the result. Do not deploy.

Full recipe, with the reasoning behind each step: https://ask-foreman.dev/recipes/merge-behind-approval`}
/>

The template mounts no merge tool at all, which is why a draft pull request is the ceiling. The GitHub extension uses an explicit allowlist, so there is no merge policy to bypass because there is no merge tool to call.

Add `mergePullRequest` to the allowlist and map it to `shipPolicy`. Merging becomes possible, and every merge waits for a person to approve it.

## When to use it

Use it when you trust the review loop enough that clicking approve on a card is the only step you want, rather than opening GitHub to merge by hand.

`shipPolicy` is the strictest policy in the template. It parks for every human caller, trusted or not, and it denies unattended runs outright. So a factory-label run still cannot merge, no matter what an issue body tells it to do.

| Caller                            | Result                    |
| --------------------------------- | ------------------------- |
| Unattended run                    | Denied                    |
| Trusted mention or Linear session | Parks on an approval card |
| Anyone else                       | Parks on an approval card |

## What it touches

| File                         | Change                                                                               |
| ---------------------------- | ------------------------------------------------------------------------------------ |
| `agent/extensions/github.ts` | Add `mergePullRequest` to `include`, and map it to `shipPolicy` in `requireApproval` |
| `evals/helpers.ts`           | Add `github__mergePullRequest` to `GITHUB_WRITE_TOOLS`                               |

<Callout type="warn" title="The eval list is not optional">
  Read-only evals assert `notCalledTool` across the whole `GITHUB_WRITE_TOOLS` list rather than naming tools one by one. A write tool missing from that list is a write tool no read-only eval is guarding.
</Callout>

## Before you run it

An approval card is a smaller thing than a merge. The card shows the tool and its input, not the diff, so clearing one is not the same as having reviewed the change. Treat it as a confirmation step and read the pull request in GitHub first.

Draft pull requests are unaffected. A draft cannot merge whatever the policy says, so the pipeline's own output still needs a person to mark it ready before merging is even on the table.

## Verify

```bash
pnpm validate
pnpm eval --tag fast
```

Then, in the `pnpm dev` TUI, ask Foreman to merge a pull request. The dev principal is untrusted, so the call should stop on an approval card with `github__mergePullRequest` pending rather than merging.

## What to expect

Step 5 of the prompt exists because the orchestrator's own instructions currently tell it that merge tools are not mounted. Leaving that in place after adding the tool gives the model two contradictory sources of truth, and the prompt usually wins.

## FAQ

<Accordions type="single">
  <Accordion title="Can an unattended run merge after this?">
    No. `shipPolicy` denies autonomous callers outright, so a factory-label run or a red-CI fix run is refused server-side rather than parked. That holds no matter what an issue body or a comment tells the agent to do, because the policy reads the stamp applied at dispatch and never model-readable content.
  </Accordion>

  <Accordion title="Why shipPolicy rather than writePolicy?">
    `writePolicy` lets trusted callers through without a card. A trusted mention could then merge with no second look, which defeats the point.

    `shipPolicy` is the only policy that parks for every human caller, trusted or not, and it is what already guards `updatePullRequest`. Merging belongs in the same class as marking a pull request ready.
  </Accordion>

  <Accordion title="What breaks if I skip the evals/helpers.ts step?">
    Nothing fails loudly, which is the problem. Read-only evals assert `notCalledTool` across the whole `GITHUB_WRITE_TOOLS` list rather than naming tools individually, so a tool missing from that list is simply not guarded. The suite would keep passing while no longer proving that read-only requests stay read-only.
  </Accordion>

  <Accordion title="Could Foreman merge a pull request it wrote itself?">
    Only after a person has already intervened twice. The pipeline opens pull requests as drafts, and a draft cannot merge. Marking it ready follows `shipPolicy` and parks, and the merge itself then parks again.

    So the sequence is a person clearing a ready card and then a person clearing a merge card. Foreman never marks its own pull request ready unprompted.
  </Accordion>

  <Accordion title="How do I undo it?">
    Remove `mergePullRequest` from `include` and from `requireApproval` in `agent/extensions/github.ts`. The tool stops existing for the model, since the allowlist has no preset behind it. Leaving the entry in `GITHUB_WRITE_TOOLS` afterwards is harmless, and it keeps the evals strict.
  </Accordion>
</Accordions>


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)