Files
claude/.claude/tasks/plans/2026-10-07-manual-push-prose-d3-1530.md
T

7.2 KiB

PLAN — manual-push-prose-d3 — REVISED r2 (3 lenses + correctness confirmation)

Contract: .claude/tasks/contracts/2026-10-07-manual-push-prose-d3-1530.md

Context

After D1 and D2 every reader of gitflow.autopush in THIS checkout (lib, emitted hooks, unpushed-guard, push-guard, banner) treats an unparseable value as manual: nothing pushes, and the stop is named. Two caveats remain: (a) onboarded projects keep their committed .githooks/ until a session-start reconcile refreshes them, so a stale per-repo hook can still push on an invalid value — the ahead count tells; (b) the soft_deny backstop names only gitflow.autopush false (settings.json is outside D3; TODO). The skill prose written during run C still says "the lib and hooks still push on an invalid value until run D". Two labels say "COMMIT + PUSH" although the step reads the push state. The release executor trusts the dispatcher's version regex alone.

Checklist

  • PRECONDITION (executor's first step): grep -l -- '--default true gitflow.autopush' lib/gitflow.sh hooks/unpushed-guard.sh hooks/session-start.sh githooks/post-commit githooks/post-merge .githooks/post-commit .githooks/post-merge must print nothing (D1 + D2 landed); any hit → STATUS: BLOCKED, nothing edited.
  • skills/capitalize/SKILL.md — line ~346 (5C invalid outcome) becomes TWO lines, both single-line bullets, placed where the one line is (still evaluated first): - **push mode \invalid`, `ahead` > 0 or unknown** → `merged to develop — : treated as manual push mode by every reader, nothing pushed (origin/develop is commit(s) behind, or unknown). Fix the value by hand, then: ! git push origin develop` (append ` once a remote exists` when `ahead` is unknown). - push mode `invalid`, `ahead` = 0 → `merged to develop — : pushed anyway, likely a stale fail-open hook (a session-start reconcile refreshes a stale .githooks/; commit the refresh) or a manual push. Fix the value by hand.`Line ~379 (STEP 6 invalid closing line) becomes two matching lines:- invalid (merged, ahead > 0 or unknown) → `⚠️ + merged to develop — : treated as manual push mode by every reader, nothing pushed (origin/develop behind). Fix the value by hand, then: ! git push origin develop` (+ ` once a remote exists` when unknown)and- invalid (merged, ahead = 0) → `⚠️ + merged to develop — : pushed anyway, likely a stale fail-open hook (refreshed by the next session-start reconcile; commit the refresh) or a manual push. Fix the value by hand.`. STEP 6 is picked by the 5C result, not evaluated in order, so disambiguate the neighbours: line ~371 auto-persisted (5C: finish rc 0 AND `ahead` = 0)→auto-persisted (push mode `auto`, finish rc 0 AND `ahead` = 0); line ~378 not on origin (merged, `ahead` unknown)→not on origin (push mode not `invalid`, merged, `ahead` unknown). Recap line ~363: merged, gitflow.autopush invalid ( behind)→merged, autopush invalid, nothing pushed ( behind) | merged, autopush invalid, pushed anyway (stale hook or manual push). Line ~375 (--no-push, branch_ahead= 0): add after the template, as an instruction like the :376 precedent:With push mode `invalid`, replace `(auto-push mode)` with `(: pushed anyway, likely a stale fail-open hook or a manual push; fix the value by hand, commit the .githooks refresh)`. Line ~376 (--no-push, branch_ahead> 0 or unknown): its existingWith push mode `invalid`, append ` gitflow.autopush= is not a boolean: fix it by hand`→With push mode `invalid`, append ` : treated as manual push mode, nothing pushed; fix the value by hand`. No "until run D" text remains; no templated : the verb's stderr line is quoted verbatim (it may say "could not read"). No period right after a ! git …` command.
  • agents/client-handover-writer.md — heading ## STEP 5 — COMMIT + PUSH (only if files changed) → ## STEP 5 — COMMIT + PUSH STATE READ (only if files changed). Residual push claims in the same step — the current text WRAPS across lines and carries bold markers; use the Read tool and multi-line old_strings: ~545-546 Before any commit or push,⏎confirm this is a gitflow repo: → Before any commit, confirm this is a gitflow repo (the pipeline never pushes):; ~553-554 **do NOT commit,⏎do NOT push.** → **do NOT commit (and never push).**; ~555-556 Commit/push skipped — no gitflow model in this repo; publish the⏎listed changes manually before deploy. → Commit skipped — no gitflow model in this repo; publish the listed changes by hand before deploy. (re-wrap as the file does). Line ~700 (C2-qualified "mini-commit; push state read, never assumed") stays. Verify with the Grep tool (pattern push), never a Bash grep carrying the push word.
  • skills/client-handover/SKILL.md — step 4 label **COMMIT + PUSH** → **COMMIT + PUSH STATE READ**; in the same sentence otherwise (manual push mode, or a hook push that failed) → otherwise (manual push mode, an invalid gitflow.autopush, or a hook push that failed).
  • agents/release-executor.md — prep span, ### Input paragraph: after "never bump it." add on its own line: Format check only, by reading the string (never inside a Bash command): <X.Y.Z> must match ^[0-9]+\.[0-9]+\.[0-9]+$ (literal regex text, single backslashes); anything else → STATUS: BLOCKED, nothing created. (prep only: finish's existing ### Preconditions already requires the release/<X.Y.Z> branch that only prep creates). Lines ~80-83: in manual push mode they stay local → in manual push mode, or with an invalid gitflow.autopush, they stay local — keep invalid gitflow.autopush and by reading the string unbroken on one physical line each (line-based greps).

Edge cases

  • Label rename: repo-wide grep for "COMMIT + PUSH" (skills, agents, lib, hooks, rules, README, USAGE, templates, CLAUDE.global.md, CHANGELOG, docs) → only the two renamed lines; in-file references are by step number.
  • "Pushed anyway" diagnosis: after D1, an invalid value with ahead = 0 can only come from a stale per-repo hook (or a human push); the line says so instead of asserting "nothing pushes".
  • Doc-sync afterwards owns: CHANGELOG [Unreleased] three sites ("treated as auto", "for this hook a non-boolean value reads as manual", "still push on it as auto"), SETTINGS Push discipline "still push on it as auto; only push-guard fails closed", with the stale-.githooks/ scope note.
  • TODO (outside D3): settings.json soft_deny names only gitflow.autopush false — add "or an unparseable value" (restriction only) in a later settings run.

Disposition

  • honors BDR-114 (fail-closed everywhere → prose must stop saying otherwise, but never claims more than the facts: the ahead count decides), BDR-100/LRN-113 (label rename with citer grep; precondition grep for D1/D2 before any prose change), LRN-104, LRN-198 (version string checked by reading, never interpolated into a check command), BDR-042 (dispatcher decides the number; the executor only formats-checks).