| name | symphony-land |
| description | Squash-merge the issue's PR once it's approved and green. Use only when the issue is in the `Merging` state — Symphony's status router gates entry. |
Land
Preconditions
- Issue state is
Merging.
gh auth status succeeds, and plain git push has credentials for the repo host.
If relying on GitHub CLI for HTTPS git credentials, run gh auth setup-git first
(gh auth setup-git --hostname <host> for a non-default host). GITHUB_TOKEN/GH_TOKEN
alone can authenticate gh pr ... commands but is not enough for the symphony-push
step's git push -u origin HEAD.
- A PR is attached to the issue and is
OPEN (not CLOSED/MERGED).
If the PR is already MERGED when entering this skill, skip the merge: record the merge SHA in the workpad and move the issue to Done.
Steps
- Inspect the PR:
gh pr view --json number,state,mergeable,mergeStateStatus,reviewDecision,statusCheckRollup
Required: state=OPEN, reviewDecision=APPROVED (or no required reviewers).
- Sync first — run the
symphony-pull skill to merge origin/main. Conflicts must be resolved in code; never leave a DIRTY/CONFLICTING PR. Re-run validation after the merge.
- Push the merge result with the
symphony-push skill.
- Wait for CI to be green. Don't rely on
gh pr merge --auto — it only blocks on checks that branch protection marks as required, and many repos either configure no required checks or only a subset. Poll gh pr checks explicitly so the gate works the same whether the repo enforces checks at the GitHub level or not — every 30s, up to 30 minutes, before merging:
for _ in $(seq 1 60); do
gh pr checks
rc=$?
case "$rc" in
0) break ;;
8) sleep 30 ;;
*) echo "PR checks failed (exit $rc)" >&2; exit 1 ;;
esac
done
[ "$rc" -eq 0 ] || { echo "Timed out waiting for checks" >&2; exit 1; }
gh pr checks exits 0 only when every check passed; 8 while any are pending; non-zero/non-8 if any failed. A failure or timeout falls into the failure-mode handler below: record the cause and move the issue to Rework.
- Squash-merge:
gh pr merge --squash
gh pr merge returning success does not guarantee the PR is merged — if the repo has a merge queue (or any auto-merge path kicks in), the command can return after enqueueing and the PR will still be OPEN while the queue runs. Verify in step 6 before continuing.
- Wait for the PR to reach terminal
MERGED state — every 15s, up to 30 minutes:
for _ in $(seq 1 120); do
state=$(gh pr view --json state -q .state)
case "$state" in
MERGED) break ;;
OPEN) sleep 15 ;;
*) echo "PR ended in unexpected state: $state" >&2; exit 1 ;;
esac
done
[ "$state" = "MERGED" ] || { echo "Timed out waiting for MERGED" >&2; exit 1; }
A CLOSED (without MERGED) state or a timeout falls into the failure-mode handler below.
- Capture the merge commit (only after
state=MERGED):
gh pr view --json mergeCommit -q .mergeCommit.oid
- The remote head branch usually auto-deletes. If not, leave it — don't delete in scripts.
- Record the merge SHA in the workpad's
### Notes and move the issue to Done.
Failure modes → Rework
If any of the following hit, record the cause in the workpad and move the issue to Rework with a brief blocker note:
- PR checks went red on the merge candidate and you can't fix locally.
- The
gh pr checks wait loop times out (still pending after 30 min).
- The merge result reintroduces a conflict you can't resolve.
gh pr merge --squash keeps failing (e.g., mergeStateStatus flips to BLOCKED/DIRTY, or the PR transitions to CLOSED without MERGED).
- The post-merge
state=MERGED wait loop times out, or the PR ends in CLOSED without MERGED (e.g., merge queue rejected it).
Do not force-push, reset, or close-and-reopen the PR to escape the failure.