Skip to content

land-deploy-sentinel mints against the cwd repo's HEAD, not the target PR's head #66

Description

@mujtaba3B

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

  1. Clone repo R, leave its checkout on some branch B.
  2. Open a PR from a different branch P (head sha != tip of B).
  3. From a session whose cwd is a parent directory (not R), invoke /land-and-deploy for that PR.
  4. Inspect <gitdir>/land-deploy-clearance: head_sha is B's tip, pr_number is empty.
  5. 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:

  1. 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.
  2. 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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions