What happened
Landing gstack-extensions#48 on 2026-08-05, /land-and-deploy was invoked and the sentinel hook minted this:
{"set_at_epoch":1785956876,"ttl_seconds":1800,"repo":"mujtaba3B/gstack-extensions",
"head_sha":"7946aebe79db1510f048ff9070fbcdabfde0f836","pr_number":"","source":"land-bash"}
7946aeb is the tip of close-out/2026-07-26, the branch that checkout happened to be sitting on. It is not the PR head, and not on the PR branch at all. pr_number came back empty.
ld_sentinel_valid treats head_sha as the strong binding, so pr-merge-gate.sh correctly returned mismatch and refused the merge. The gate did its job. The problem is that the mint fed it the wrong fact.
Root cause
hooks/scripts/land-deploy-sentinel.sh:102
head_sha=$(git -C "$workdir" rev-parse HEAD 2>/dev/null || echo "")
The sentinel binds to whatever commit the working directory is on. That is only the PR head when the ceremony happens to be driven from a checkout already sitting on the PR branch. Drive it from a session anchored elsewhere (~/dev, with the target repo on an unrelated branch) and it arms for an unrelated commit.
The script's own header comments already describe this failure class ("the cwd-keyed mint wrote the sentinel..."), so the shape is known. This is a concrete instance.
Why it matters
Not a rare setup. Any land driven from a parent workspace session, or from a repo whose checkout is mid-work on another branch, hits it. The failure is safe (it blocks rather than allows) but it stops a legitimate merge with a message that reads like staleness, sending you to re-run /land-and-deploy, which from the same cwd reproduces the identical wrong binding.
Repro
- Clone repo R, leave its checkout on some branch B.
- Open a PR from a different branch P (head sha != tip of B).
- From a session whose cwd is a parent directory (not R), invoke
/land-and-deploy for that PR.
- Inspect
<gitdir>/land-deploy-clearance: head_sha is B's tip, pr_number is empty.
merge-clearance check reports land-deploy sentinel: mismatch.
Suggested fix
Bind to the target PR's head rather than the workdir's:
- When a PR number is resolvable, take
gh pr view <n> --json headRefOid -q .headRefOid as head_sha, and record the real pr_number.
- When it is not resolvable, either keep the workdir HEAD but mark the sentinel weakly-bound, or refuse to mint and say why, rather than minting a confidently wrong binding.
Cheap intermediate: if the resolved PR head and the workdir HEAD disagree, log it at mint time. Today the disagreement is silent until the merge is blocked.
Workaround used
Detached the target checkout to the PR head, re-invoked /land-and-deploy so the hook minted against the right commit, merged, then restored the original branch. It works, but it means temporarily moving a checkout that may have in-flight work.
Second finding (confirmed live while filing this issue)
The merge guard matches on command text, not on an executed command. Two consequences:
- A compound
merge-clearance clear && gh pr merge ... is blocked before clear ever runs, so with the 600s clearance TTL the two must be separate consecutive calls.
- Writing this very issue was blocked, because the body quotes the string
gh pr merge while describing the bug. Documenting the guard trips the guard.
The repo handbook already knows the workaround (split the pattern in the outer command). Worth either narrowing the matcher to command position, or documenting the false positive where people will hit it.
What happened
Landing gstack-extensions#48 on 2026-08-05,
/land-and-deploywas invoked and the sentinel hook minted this:{"set_at_epoch":1785956876,"ttl_seconds":1800,"repo":"mujtaba3B/gstack-extensions", "head_sha":"7946aebe79db1510f048ff9070fbcdabfde0f836","pr_number":"","source":"land-bash"}7946aebis the tip ofclose-out/2026-07-26, the branch that checkout happened to be sitting on. It is not the PR head, and not on the PR branch at all.pr_numbercame back empty.ld_sentinel_validtreatshead_shaas the strong binding, sopr-merge-gate.shcorrectly returnedmismatchand refused the merge. The gate did its job. The problem is that the mint fed it the wrong fact.Root cause
hooks/scripts/land-deploy-sentinel.sh:102head_sha=$(git -C "$workdir" rev-parse HEAD 2>/dev/null || echo "")The sentinel binds to whatever commit the working directory is on. That is only the PR head when the ceremony happens to be driven from a checkout already sitting on the PR branch. Drive it from a session anchored elsewhere (
~/dev, with the target repo on an unrelated branch) and it arms for an unrelated commit.The script's own header comments already describe this failure class ("the cwd-keyed mint wrote the sentinel..."), so the shape is known. This is a concrete instance.
Why it matters
Not a rare setup. Any land driven from a parent workspace session, or from a repo whose checkout is mid-work on another branch, hits it. The failure is safe (it blocks rather than allows) but it stops a legitimate merge with a message that reads like staleness, sending you to re-run
/land-and-deploy, which from the same cwd reproduces the identical wrong binding.Repro
/land-and-deployfor that PR.<gitdir>/land-deploy-clearance:head_shais B's tip,pr_numberis empty.merge-clearance checkreportsland-deploy sentinel: mismatch.Suggested fix
Bind to the target PR's head rather than the workdir's:
gh pr view <n> --json headRefOid -q .headRefOidashead_sha, and record the realpr_number.Cheap intermediate: if the resolved PR head and the workdir HEAD disagree, log it at mint time. Today the disagreement is silent until the merge is blocked.
Workaround used
Detached the target checkout to the PR head, re-invoked
/land-and-deployso the hook minted against the right commit, merged, then restored the original branch. It works, but it means temporarily moving a checkout that may have in-flight work.Second finding (confirmed live while filing this issue)
The merge guard matches on command text, not on an executed command. Two consequences:
merge-clearance clear && gh pr merge ...is blocked beforeclearever runs, so with the 600s clearance TTL the two must be separate consecutive calls.gh pr mergewhile describing the bug. Documenting the guard trips the guard.The repo handbook already knows the workaround (split the pattern in the outer command). Worth either narrowing the matcher to command position, or documenting the false positive where people will hit it.