# PLAN — manual-push-skills-c2 — REVISED r2 (3 lenses + correctness confirmation)
Contract: .claude/tasks/contracts/2026-10-07-manual-push-skills-c2-1325.md
## Context
Same pattern as C1: since BDR-095 the hooks push every commit in auto-push mode, so a skill's own `git push` (and the question that gates it) gates nothing in auto mode, and in manual/invalid mode push-guard denies it. Truth about "on origin" comes from a FACT read after the fact — `git rev-list --count origin/
..
` (0 = on origin; >0 = not; `unknown` = no remote-tracking ref) — never from the mode word (the lib still pushes on an invalid value until run D). The verb `gitflow.sh push-mode` (C1) only WORDS the explanation (manual vs push FAILED) and is read in its own Bash call; no shell variable crosses calls. Where a user must push, the hint is a complete `! git …` command with `-u` and, for multi-repo flows, `-C `.
## Checklist
- [ ] agents/client-handover-writer.md — define ONE reusable paragraph "PUSH STATE READ" (insert it once, right after the commit-change dispatch in STEP 5, and REFER to it elsewhere): "Three separate Bash calls, never combined, read-only: `git branch --show-current` → `
`; `git remote get-url origin >/dev/null 2>&1 && echo origin || echo no-origin`; `git rev-list --count origin/
..
2>/dev/null || echo unknown` → `ahead`. If `ahead` ≠ 0 and origin exists: `bash "$HOME/.claude/lib/gitflow.sh" push-mode` → anything other than `auto` is treated like `manual` (stderr line kept verbatim when `invalid`). State: `ahead` = 0 → `on origin`; no commits were made this run or the gitflow fallback left changes uncommitted → `nothing to push (no commits this run)` / `uncommitted changes (no gitflow model): publish by hand`; `no-origin` → `not on origin (no origin remote: add one first)`; `ahead` > 0 or `unknown` → `pending — you: ! git push -u origin
` + reason: push mode `manual` → `(manual push mode)`, `auto` → `(not on origin: no remote-tracking ref or the hook push did not land)`, `invalid` → `()`. The pipeline never runs `git push` itself." Then:
STEP 5: replace ONLY lines ~570-578 (from "Then, **before pushing, STOP and ask for an explicit GO**" through the "Only on **A** … then continue." paragraph) with the PUSH STATE READ paragraph followed by: "`pending` → tell the user NOW: `Commits are local only. Push first: ! git push -u origin
`." KEEP the red-flag box (~580-582) and reword it (multi-line old_string, exact current text: `> **Red flag — STOP:** never \`git push\` without option-A GO; never\n> \`gitflow finish\`/\`merge\`. This pipeline commits and (on GO) pushes a working\n> branch — it never integrates into a protected branch.`) → `> **Red flag — STOP:** never \`git push\` (the hooks push in auto-push mode;\n> otherwise the user does); never \`gitflow finish\`/\`merge\`. This pipeline\n> commits a working branch — it never integrates into a protected branch.` Then DELETE lines ~584-598 (the `CURRENT_BRANCH=…/git push origin` bash block and the "If push fails …" AskUserQuestion block).
STEP 6: FIRST line of STEP 6 (before "Skip if PROJECT_TYPE != web"): "Re-run PUSH STATE READ (every path reaches STEP 6, some without STEP 5's read)." Deploy brief (lines ~626-631, multi-line anchors: `"Push has been\n done. The platform deploys automatically — usually 1-3 min. Watch the\n dashboard.`): when the state is `pending` the brief OPENS with `First push: ! git push -u origin
`; the Vercel/Netlify/Cloudflare line reads "The platform deploys automatically after your push (a working branch gives a preview at most; production builds from the production branch) — usually 1-3 min…"; the CI line "Workflow `` runs on your push…"; when `on origin`, keep "Push has been done. …". After option A "Deployed" (~648): "Re-run PUSH STATE READ; still `pending` → ask again (the live site cannot hold these commits)."
Reports: PIPELINE STOPPED template (~795-810) gains a line at column 0 `Push: ` after the Score table; the 9.7 user report gains a bullet `- Push: `; both re-run PUSH STATE READ right before printing (never a STEP 5 snapshot). Line ~65 `3. Commit + push if files changed.` → `3. Commit if files changed (the hooks push in auto-push mode; the push state is read, never assumed).`; lines ~686-687 `(mini-commit\n+ push)` → `(mini-commit; push state read, never assumed)`.
- [ ] skills/client-handover/SKILL.md step 4 — `run /commit-change (atomic logical commits) then \`git push\`.` → `run /commit-change (atomic logical commits); the gitflow hooks push in auto-push mode, otherwise (manual push mode, or a hook push that failed) the agent tells the user to push with \`! git push -u origin \` BEFORE the deploy pause.`
- [ ] skills/release-candidate/SKILL.md STEP 6 — replace the paragraph from "`main` and `develop` are already on origin" through the `hold` line (lines ~96-108) with:
"Read the state, separate Bash calls: `git rev-list --count origin/main..main 2>/dev/null || echo unknown`, `git rev-list --count origin/develop..develop 2>/dev/null || echo unknown`, `bash "$HOME/.claude/lib/gitflow.sh" push-mode`.
- anything other than `auto` from the verb (manual, invalid, empty, usage error) OR either count ≠ 0 or `unknown` → Claude pushes nothing (push-guard would refuse it in manual mode; a failed lib push is the user's call, BDR-095). Print ONE command for the user and STOP, no question: `! git push --atomic origin main develop v` (invalid: quote the verb's stderr line verbatim; auto with a count ≠ 0 or unknown: say `main/develop not on origin (no remote-tracking ref or the lib's push did not land)`; no origin remote (`git remote get-url origin` fails): say `add an origin remote first`).
- push mode `auto` and both counts 0 → main and develop are on origin; only the tag is left. STOP. On explicit go only ([[LRN-069]]) — run the tag push HERE, never delegated: `AskUserQuestion: Push tag v to origin? — go / hold`. Go → ```bash\ngit push origin v\n```. `hold` → stop; the release is on origin (main + develop), the tag stays local until the next push of main (`--follow-tags` on every lib and hook push)."
Overview lines ~27-28 (multi-line anchor `and the two human gates (when to release, and\nthe tag push).`) → append " (auto-push mode; in manual push mode the user pushes main, develop and the tag in one command)". Common-mistakes bullet list: add `- Pushing anything in manual push mode → print the one user command, push nothing.` Frontmatter description ("tag it, and push") and the STEP 6 heading stay (frozen, residuals).
- [ ] agents/release-executor.md — lines ~80-82 (multi-line anchor: `Finish has already pushed \`main\` and\n \`develop\` through the lib's hooks (BDR-095); the tag stays local until\n the dispatcher's tag-push gate.`) → "In auto-push mode finish has already pushed `main` and `develop` through the lib (BDR-095); in manual push mode they stay local. The tag stays local"; lines ~85-86 (anchor `\`main\`/\`develop\` ride the lib's hook\npushes during finish;`) → "`main`/`develop` ride the lib's pushes during finish in auto-push mode".
- [ ] skills/tour/SKILL.md — Rules (lines ~273-275, multi-line anchor: ` The chore branch's own commits are pushed by the gitflow hooks\n (BDR-095); a \`push FAILED\` hook warning is a report residual, fixed\n with a plain \`git push -u origin chore/tour-\`.`) → " The gitflow hooks push the chore branch in auto-push mode only; when it is not on origin (manual push mode, or a `push FAILED` warning) the USER pushes it — `! git -C push -u origin ` — the tour never pushes or retries." STEP 3 per-project closing list (~228-239): add item 5 AFTER the `docs(tour): report` commit (3.3, the last commit): "5. Push state, one read-only call: `git -C rev-list --count --not --remotes 2>/dev/null || echo unknown` (`` = the name `gitflow start` returned, suffixed `-2`/`-3` on a same-day re-run — never the bare `chore/tour-`). 0 → `on origin`; else `local only → ! git -C push -u origin ` (no origin remote → `local only (no origin remote)`)." Summary row format (~258-259): after `, commits` append ` | on origin` or ` | local only → ! git -C push -u origin `. Runner prompt (~92-100) unchanged: the row format carries the field and the runner already returns `BRANCH: `. No verb read in the tour.
## Edge cases
- `unknown` (never fetched) → "not on origin (no remote-tracking ref)"; no origin remote → "add an origin remote first" (the `! git push … origin …` hint would fail); never "push FAILED". `ahead` = 0 → "on origin" with no claim about WHO pushed (in manual mode it was the user).
- Every `Push:`/deploy-brief statement re-reads the fact right before it prints (a STEP 5 snapshot is stale once the user pushed); STEP 6 reads it first because three paths reach STEP 6 without STEP 5's read (no pending changes; gitflow fallback; `--skip-audits`).
- AC substrings must each sit on ONE physical line (line-based greps); `Push:` at column 0 inside the PIPELINE STOPPED fence; `auto-push mode` on two distinct lines in release-executor.md.
- Invalid value: never "not pushed" from the mode; the counts decide; the verb's stderr is quoted verbatim (it may say "could not read" without a value).
- Release command is `--atomic`: a non-fast-forward on main rejects the whole set, so the tag never lands without its merge.
- client-handover-writer runs INLINE in the main loop (SKILL.md:29-33): the prose reaches the pusher. commit-change and handover-doc-writer never push.
- Tour runners are sub-agents using `git -C `: the fact call uses `-C` too; the user hint carries the path (same branch name across projects).
- Removed gates (client-handover GO question, release "on origin" claim) were gating nothing in auto mode: the hooks had pushed already (same redundancy C1 removed in /close). LRN-069's push gate now means: Claude never pushes in these flows except the release tag on explicit go in auto mode.
- Residual (frozen by AC6): release-candidate frontmatter "tag it, and push", STEP 6 heading "Tag push GATE (ASK)" — true in auto mode; listed in the CHANGELOG at doc-sync.
## Disposition
- honors BDR-095 (truth from the remote state; a failed push is the user's decision), BDR-111/BDR-112 (verb for wording only, read in its own call; zero `git config` in skills; zero `git push` inside a Bash call reachable in manual mode), BDR-042 (tag + its gate stay in the dispatcher), LRN-069 (explicit go kept for the one push Claude still makes: the tag, auto mode), LRN-193 (fresh confirmation pass after this revision), LRN-104 (every user-facing string is in the skill text; no runtime test exists for prose — AC6 is the reading gate).