jouet pr land
jouet pr land merges a pull request that jouet pr next says is ready, and confirms that it merged. It follows steipete's /landpr, adapted so that nothing can be merged by accident: it refuses anything that is not ready or not tested, never force-pushes, and never bypasses branch protection.
jouet pr land [PR] [--repo OWNER/NAME] [--method squash|merge|rebase] [--update]
[--yes] [--dry-run] [--timeout DUR] [--interval DUR] [--settle DUR]The flags are listed in the CLI reference. PR is a number, a URL, owner/repo#123 or a branch, and defaults to the current branch's pull request.
What it does
Refuse unless ready. It fetches the pull request as
jouet pr nextdoes. Unless the answer isready_to_merge, it exits4and says what is missing and what to run next. With--update,behind_baseis accepted too.Update, with
--update. A branch that is behind its base is updated on GitHub with the update-branch endpoint, which merges the base into it. jouet never rebases or force-pushes. The request names the head that was checked, so a push in between makes GitHub refuse it.Wait for checks. After an update, it waits for the new head's checks with the same waiting as
jouet wait ci, up to--timeout(default30m). A failed check stops it. Then the pull request goes through step 1 again, since an update can dismiss an approval.Merge. It merges with
--method(defaultsquash) through the merge endpoint, naming the head SHA it checked. This is the merge a person gets from the button: branch protection applies, and nothing asks to bypass it. If GitHub refuses, jouet reports GitHub's reason and exits4.A pull request in one of GitHub's native stacks (the ones
gh stackmakes) cannot use the merge endpoint, so jouet merges it through GitHub's asynchronous merge endpoint instead, with the same head SHA, as a direct merge rather than through a merge queue, and waits for the result. That endpoint merges every pull request below the one named as well, so jouet lands only the bottom open pull request of a stack: one whose base is still another pull request's branch is refused with exit4. Land the one below it first, or the whole stack withgh stack merge.Confirm. It reads the pull request back until GitHub reports it merged, and prints the merge commit. If the response to the merge was lost, this step finds out whether it happened; the merge itself is never sent twice.
$ jouet pr land 7 --repo jdx/demo --yes
jouet: squash-merge jdx/demo#7 at aaaaaaa111
merged jdx/demo#7 (squash) as ccccccc3333333333333333333333333333333333
https://github.com/jdx/demo/pull/7Tests before landing
ready_to_merge considers the pull request's reported checks and branch protection. Those checks may cover only part of the suite, and a repository may have none. jouet does not require a local full run before landing; the stop hook accepts a passing partial test run.
Asking first
jouet pr land is the one jouet command that can ask a question, and it asks only a person:
--dry-runprints the plan and changes nothing.--yesmerges without asking.- Neither: when stdin and stderr are both terminals and no coding agent is detected, it shows the plan on stderr and asks
Merge OWNER/NAME#N? [y/N]. Onlyyoryesgoes ahead. Anyone else, an agent at a terminal included, gets a usage error (exit2) before anything is sent to GitHub. Agents are detected the same way as for display modes;--displayandJOUET_DISPLAYdo not count, because they choose how output looks, not who is there to answer.--jsonnever asks either.
$ jouet pr land 7 --repo jdx/demo --dry-run --update --method merge
jouet pr land would:
1. merge main into feat on GitHub (update-branch, never a force-push)
2. wait up to 30m for the checks on the new head
3. merge jdx/demo#7 at the new head through the API, honouring branch protection
4. confirm jdx/demo#7 is merged
dry run: nothing was changedJSON
{
"repo": "jdx/demo",
"pr": 7,
"url": "https://github.com/jdx/demo/pull/7",
"result": "merged",
"method": "squash",
"head_sha": "aaaaaaa111",
"plan": [
"squash-merge jdx/demo#7 at aaaaaaa111 through the API, honouring branch protection",
"confirm jdx/demo#7 is merged"
],
"merge_commit": "ccccccc3333333333333333333333333333333333"
}After --update, updated is true, head_sha is the new head, and ci is the jouet wait ci document for its checks, failure logs included. result is merged, dry_run, or, when the checks after --update did not pass, ci_failed, conflict or timeout. A refusal is an error document, {"error": {"code": "precondition", "message": "...", "hint": "..."}}, like any other.
Exit codes
| Code | When |
|---|---|
0 | Merged and confirmed, or a dry run of a pull request that is ready |
1 | The checks failed after --update; nothing was merged |
2 | Bad arguments, or neither --yes nor --dry-run with no person to ask |
3 | The pull request was not found |
4 | Refused: not ready to merge, no full test pass recorded at the head, not confirmed at the prompt, the head moved, a stacked pull request above one that is not merged, or GitHub refused the update or the merge (branch protection, a method the repository does not allow, missing permission). Nothing was merged |
5 | Authentication, network or rate-limit failure, or GitHub did not report the merge |
124 | The checks after --update were still running at --timeout |