Files
command-center/agent/managed-skills/land-merged-pr-with-blocked-tree-ops/SKILL.md
T

5.7 KiB

name, description
name description
land-merged-pr-with-blocked-tree-ops Use when a merged PR must land in a checkout whose working tree is dirty and workspace policy blocks git checkout/restore/stash: commit-the-dirt pattern, the secret-gate false positive that blocks the landing commit, conflict resolution proven settings-vs-comments, and heartbeat (live-sync) handoff.

Landing a merged trunk into a checkout that IS the deployment (dank/command-center ~/.omp), when tracked files are dirty and policy forbids checkout/restore/stash on shared state. Re-earned 2026-10-21 landing PRs #61 + #63 into ~/.omp (27 commits behind, two conflicts, one gate refusal).

1. Classify the dirt before touching it

git status --porcelain then compare against what the merge brings in — only overlap blocks the merge:

{ git diff --name-only; git diff --cached --name-only; } | sort -u > /tmp/dirty.txt
git diff --name-only HEAD origin/main | sort -u > /tmp/incoming.txt
comm -12 /tmp/dirty.txt /tmp/incoming.txt     # these paths must be clean

Note the first column of porcelain: M = staged, M = unstaged. A staged file shows nothing in git diff — look at --cached too, or you will "verify" a file is unchanged while it is staged. comm needs both inputs sorted; unsorted input prints "file is not in sorted order" and a partial answer.

2. Prove a dirty file content-free before resolving

cmp it against the trunk blob, never against memory:

git show origin/main:$f > /tmp/incoming
cmp -s "$f" /tmp/incoming && echo "IDENTICAL to trunk"   # commit/discard is content-free

If identical, the dirt is a sync artifact, not user work. Say so in the commit message with the cmp result — that is what makes a direct commit on main defensible for a prose-tier file.

3. Commit rather than discard (policy-blocked verbs)

With checkout/restore/stash unavailable, the only non-destructive unblock is a commit. Two separate commits, and order matters:

  1. Prose-tier dirt proven byte-identical to trunk — commit alone, message states cmp proved no prose change.
  2. Machine tiers (agent/memories/**, agent/managed-skills/**, agent/config.yml, agent/models.yml, dotfiles/) — let the heartbeat do it: LC_ALL=C scripts/live-sync.sh stages allowlist only, commits, merges trunk, pushes, and exits 0 even when it aborts the merge on conflict (the alert goes to stderr + ntfy). exit 0 does not mean landed — check behind= afterwards.

Do NOT git add -A: the index may already hold dozens of staged machine-tier files, and git add <one file> does not clear them — they ride along into your commit.

4. The gate refusal that blocks the landing commit

The pre-commit secret gate scans the staged diff, so a single-file commit still trips on previously staged content. It fires on inert placeholders inside the skill docs that document the placeholder (e.g. AKIA0000000000000EXAMPLE — the AWS all-zero example id in git-secret-gate-hook / harden-precommit-secret-gate). If the checkout is behind, it is running the pre-fix hook while the fix sits unlanded in trunk: the landing is blocked by the very bug the PR corrects.

Audit first, then override deliberately — never --no-verify:

git diff --cached | grep -nE 'AKIA[A-Z0-9]{12,}|sk-[A-Za-z0-9]{16,}|ghp_[A-Za-z0-9]{20,}|glpat-[A-Za-z0-9]{16,}|BEGIN [A-Z ]*PRIVATE KEY|eyJ[A-Za-z0-9_-]{20,}'
git diff --cached | grep -oE 'AKIA[A-Z0-9]{12,}|sk-[A-Za-z0-9]{16,}' | sort | uniq -c   # what exactly, how many
ALLOW_SECRETS=1 git commit -F msg    # message records the audit + that the gate ran

Record in the message: the hits are inert placeholders, which files they sit in, that this checkout runs the pre-fix hook, and that the refusal dies with this landing.

5. Merge conflicts: resolve by proving settings identical

git merge --no-commit --no-ff origin/main
git diff --name-only --diff-filter=U

For each conflicted file, get all three blobs (HEAD, merge base, origin/main) and ask what the local side uniquely holds:

  • Prose / doc pages: diff ours vs trunk; if ours only lacks the incoming trim, take trunk.
  • Real settings files (kdl/yaml/env): strip comments and diff — diff <(grep -v '^\s*//' ours) <(grep -v '^\s*//' theirs). Empty output ⇒ only comments differ ⇒ taking trunk changes no deployed behaviour, and you can say that in the merge message. (2026-10-21: desk-1-home.kdl — modes byte-identical, trunk's comment corrected the false "no 240 Hz mode exists".)

Resolve without the blocked verbs by writing the trunk blob: git show origin/main:$f > $f && git add $f. Then validate the live artifact (niri validate, the suite) before committing the merge, and push.

6. Verify the push actually landed

git push output can be swallowed when wrapped in command substitution inside a batch — verify from the remote, not from the push:

git fetch -q origin && printf 'HEAD=%s origin/main=%s behind=%s\n' \
  "$(git rev-parse --short HEAD)" "$(git rev-parse --short origin/main)" "$(git rev-list --count HEAD..origin/main)"

7. Close and prune

  • Close the issues with a comment giving merge SHA + PR link + title (numbers are per-repo; a bare #N is not a reference).
  • Prune guards, all three before git worktree remove --force: ls -l /proc/*/cwd | grep -c '<wt-name>' must be 0; git -C <wt> status --porcelain | wc -l must be 0; git rev-list --count main..<branch> must be 0. Check for .AGENTTRASH/** inside the worktree first — removal destroys the only copy.
  • git branch -d (not -D) once the branch is reached by main; git push origin --delete <branch> is allowed on the local Gitea.
  • Delete the temp -K token config file in the same session that created it.