User-Approved Plan State Sync
Use when a plan has been user-approved and GitHub/local planning surfaces need to be synchronized. This includes both cases where the GitHub issue already reflects approval (status:plan-approved) and cases where the user has just explicitly approved the issue and asks to create the approval marker / move the label.
When to use
- User says they approve an issue/plan and asks to create
.planning/plan-approved/<issue>.md, move labels to status:plan-approved, or proceed to implementation.
- User says they already approved the recommendation/plan.
- User says approval was done "via label", "label-based", or similar, meaning the live GitHub
status:plan-approved label is the approval source.
- GitHub issue already has
status:plan-approved.
- Local repo is missing one or more of:
.planning/plan-approved/<issue>.md
- local plan header
> **Status:** plan-approved
docs/plans/README.md row with plan-approved
Goal
Do approval-state synchronization, not rollback. Bring GitHub labels, the local marker, canonical plan status, and the planning index into agreement that the issue is approved and ready for implementation.
Steps
Verify live GitHub state first.
gh issue view <issue> --json number,title,labels,state,url
- Confirm the issue is still open.
- If
status:plan-approved is already present, treat the live label as the approval source.
- If the issue is still
status:plan-review but the current user message explicitly approves it, treat the user message as the approval source and move the label to status:plan-approved after local marker creation.
Verify local drift surfaces.
- Check
.planning/plan-approved/<issue>.md
- Check the canonical plan file header status
- Check the
docs/plans/README.md row status
Synchronize approval state.
- Create/update
.planning/plan-approved/<issue>.md.
- Record the approval source precisely:
- current explicit user message, if the user approved in this session; or
- live GitHub
status:plan-approved label, if label-based approval was already present.
- Update local plan header from
draft/plan-review to plan-approved.
- Update the
docs/plans/README.md row from draft/plan-review to plan-approved (implementation ready) when implementation is now authorized.
- If GitHub is not already approved and the user explicitly approved now, run
gh issue edit <issue> --add-label status:plan-approved --remove-label status:plan-review.
- If GitHub is already
status:plan-approved, sync local state instead of redundantly editing labels.
Commit and push the approval-sync surfaces before posting the GitHub sync comment when possible.
- Stage only the marker, canonical plan, and planning index rows for that issue.
- Push the commit so the comment can cite a durable commit hash.
- If unrelated local dirt remains, keep it out of the commit and mention it only as unrelated checkout state in the final response if relevant.
Post a short GitHub comment noting approval-state sync.
Include that local approval evidence was reconciled to match live GitHub approval state.
Include the approval-sync commit hash after it is pushed.
If the issue is now execution-ready, include the next concrete TDD execution package in the same comment: target test file(s), implementation script/module, config/artifact outputs, and required validation/review gates. This turns the approval-sync comment into a handoff that Codex/Codex can immediately execute without re-discovery.
Re-verify all four surfaces plus the remote hash.
- GitHub labels
- local approval marker
- plan header
- README row
git ls-remote origin refs/heads/main matches local HEAD for the sync commit
Important rule
Do not roll an issue back to status:plan-review just because the local marker or README row lags, if the user has already approved and GitHub is already correctly at status:plan-approved.
If the user says approval was label-based and the pre-check shows status:plan-approved is already present, do not redundantly edit labels. Treat the label as the approval source, sync local artifacts to it, and document that source in the marker/comment.
Mid-stream revalidation rule
If an earlier handoff or recommendation said to remove a stale approval marker or keep the issue in status:plan-review, revalidate live GitHub immediately before staging/committing. If the live issue now carries status:plan-approved, switch course to approval-state sync instead of committing a rollback/review-state change.
Use the live status:plan-approved label as the approval source in the local marker only when it is verified directly in the current session. Prefer neutral wording such as:
Approved by: user
Approval source: live GitHub issue label status:plan-approved observed during approval-state sync
Approved at: <current UTC timestamp>
Issue: #<issue>
Plan: <plan path>
Review evidence: <provider verdict summary and artifact paths>
After the sync, update any stale plan sections that still describe older MAJOR/UNAVAILABLE review artifacts or stale approval drift. Otherwise the plan can contradict the restored approval marker and trigger another review/governance churn cycle.
When committing from a dirty checkout, stage only the issue's approval-sync surfaces. If a shared index file (for example docs/plans/README.md) also contains unrelated dirty rows, temporarily restore those unrelated rows to HEAD before staging, commit the narrow sync, then restore the user's unrelated local dirt afterward.
Concurrent-git / push verification gotchas
Approval-sync work often happens in a busy multi-agent checkout. Before committing:
- If
git add/git commit fails on .git/index.lock, do not immediately delete the lock blindly. First run ps -ef | grep -E 'git( |$)' | grep -v grep and identify live git/status processes.
- If a live
git status, git worktree add, git reset, or other git process is still running, wait briefly or let it finish; only remove .git/index.lock after confirming no relevant git process remains.
- If
git push is rejected as non-fast-forward, immediately run git fetch origin main and inspect git show --stat --oneline origin/main plus the remote versions of the marker/plan/README row. Another agent may already have pushed an equivalent approval-sync commit. If origin/main already contains the required approval surfaces, treat that remote commit as the durable sync, post the GitHub comment citing it, and do not continue a duplicate local rebase/commit.
- If a duplicate local approval-sync commit is already in progress while
origin/main has equivalent surfaces, preserve unrelated dirt first, then either git rebase --abort or hard-align only after confirming no unique local work would be lost. Prefer using the remote commit hash as source of truth over forcing a duplicate push.
- If
git push reports remote rejected ... cannot lock ref ... is at <new> but expected <old>, treat it as an ambiguous push outcome, not an automatic failure. Immediately verify with git rev-parse HEAD and git ls-remote origin refs/heads/main. If both hashes match, the push actually landed and no retry is needed.
- Keep final verification anchored to the four approval surfaces plus remote hash: GitHub label, approval marker, plan header, README row, and the pushed/observed remote commit hash.
origin/main == HEAD is ideal, but in a dirty/concurrent checkout it is acceptable for origin/main to contain the verified approval-sync commit while local HEAD has unrelated divergence; report that distinction explicitly.
Example artifact updates
.planning/plan-approved/2269.md
docs/plans/2026-04-15-issue-2269-openfoam-v2312-baseline-workflow-and-validation.md
docs/plans/README.md
Output expectation
After sync, local and remote state should agree that the issue is approved and ready for execution.
1---2name: user-approved-plan-state-sync3description: Reconcile GitHub and local repo state when a plan has been user-approved, including direct approval messages that require creating the local marker and moving the issue to status:plan-approved.4---56# User-Approved Plan State Sync78Use when a plan has been user-approved and GitHub/local planning surfaces need to be synchronized. This includes both cases where the GitHub issue already reflects approval (`status:plan-approved`) and cases where the user has just explicitly approved the issue and asks to create the approval marker / move the label.910## When to use11- User says they approve an issue/plan and asks to create `.planning/plan-approved/<issue>.md`, move labels to `status:plan-approved`, or proceed to implementation.12- User says they already approved the recommendation/plan.13- User says approval was done "via label", "label-based", or similar, meaning the live GitHub `status:plan-approved` label is the approval source.14- GitHub issue already has `status:plan-approved`.15- Local repo is missing one or more of:16 - `.planning/plan-approved/<issue>.md`17 - local plan header `> **Status:** plan-approved`18 - `docs/plans/README.md` row with `plan-approved`1920## Goal21Do approval-state synchronization, not rollback. Bring GitHub labels, the local marker, canonical plan status, and the planning index into agreement that the issue is approved and ready for implementation.2223## Steps241. Verify live GitHub state first.25 - `gh issue view <issue> --json number,title,labels,state,url`26 - Confirm the issue is still open.27 - If `status:plan-approved` is already present, treat the live label as the approval source.28 - If the issue is still `status:plan-review` but the current user message explicitly approves it, treat the user message as the approval source and move the label to `status:plan-approved` after local marker creation.29302. Verify local drift surfaces.31 - Check `.planning/plan-approved/<issue>.md`32 - Check the canonical plan file header status33 - Check the `docs/plans/README.md` row status34353. Synchronize approval state.36 - Create/update `.planning/plan-approved/<issue>.md`.37 - Record the approval source precisely:38 - current explicit user message, if the user approved in this session; or39 - live GitHub `status:plan-approved` label, if label-based approval was already present.40 - Update local plan header from `draft`/`plan-review` to `plan-approved`.41 - Update the `docs/plans/README.md` row from `draft`/`plan-review` to `plan-approved (implementation ready)` when implementation is now authorized.42 - If GitHub is not already approved and the user explicitly approved now, run `gh issue edit <issue> --add-label status:plan-approved --remove-label status:plan-review`.43 - If GitHub is already `status:plan-approved`, sync local state instead of redundantly editing labels.44454. Commit and push the approval-sync surfaces before posting the GitHub sync comment when possible.46 - Stage only the marker, canonical plan, and planning index rows for that issue.47 - Push the commit so the comment can cite a durable commit hash.48 - If unrelated local dirt remains, keep it out of the commit and mention it only as unrelated checkout state in the final response if relevant.49505. Post a short GitHub comment noting approval-state sync.51 Include that local approval evidence was reconciled to match live GitHub approval state.52 Include the approval-sync commit hash after it is pushed.53 If the issue is now execution-ready, include the next concrete TDD execution package in the same comment: target test file(s), implementation script/module, config/artifact outputs, and required validation/review gates. This turns the approval-sync comment into a handoff that Codex/Codex can immediately execute without re-discovery.54556. Re-verify all four surfaces plus the remote hash.56 - GitHub labels57 - local approval marker58 - plan header59 - README row60 - `git ls-remote origin refs/heads/main` matches local `HEAD` for the sync commit6162## Important rule63Do not roll an issue back to `status:plan-review` just because the local marker or README row lags, if the user has already approved and GitHub is already correctly at `status:plan-approved`.6465If the user says approval was label-based and the pre-check shows `status:plan-approved` is already present, do not redundantly edit labels. Treat the label as the approval source, sync local artifacts to it, and document that source in the marker/comment.6667## Mid-stream revalidation rule6869If an earlier handoff or recommendation said to remove a stale approval marker or keep the issue in `status:plan-review`, revalidate live GitHub immediately before staging/committing. If the live issue now carries `status:plan-approved`, switch course to approval-state sync instead of committing a rollback/review-state change.7071Use the live `status:plan-approved` label as the approval source in the local marker only when it is verified directly in the current session. Prefer neutral wording such as:7273```text74Approved by: user75Approval source: live GitHub issue label status:plan-approved observed during approval-state sync76Approved at: <current UTC timestamp>77Issue: #<issue>78Plan: <plan path>79Review evidence: <provider verdict summary and artifact paths>80```8182After the sync, update any stale plan sections that still describe older MAJOR/UNAVAILABLE review artifacts or stale approval drift. Otherwise the plan can contradict the restored approval marker and trigger another review/governance churn cycle.8384When committing from a dirty checkout, stage only the issue's approval-sync surfaces. If a shared index file (for example `docs/plans/README.md`) also contains unrelated dirty rows, temporarily restore those unrelated rows to HEAD before staging, commit the narrow sync, then restore the user's unrelated local dirt afterward.8586### Concurrent-git / push verification gotchas8788Approval-sync work often happens in a busy multi-agent checkout. Before committing:89- If `git add`/`git commit` fails on `.git/index.lock`, do not immediately delete the lock blindly. First run `ps -ef | grep -E 'git( |$)' | grep -v grep` and identify live git/status processes.90- If a live `git status`, `git worktree add`, `git reset`, or other git process is still running, wait briefly or let it finish; only remove `.git/index.lock` after confirming no relevant git process remains.91- If `git push` is rejected as non-fast-forward, immediately run `git fetch origin main` and inspect `git show --stat --oneline origin/main` plus the remote versions of the marker/plan/README row. Another agent may already have pushed an equivalent approval-sync commit. If `origin/main` already contains the required approval surfaces, treat that remote commit as the durable sync, post the GitHub comment citing it, and do not continue a duplicate local rebase/commit.92- If a duplicate local approval-sync commit is already in progress while `origin/main` has equivalent surfaces, preserve unrelated dirt first, then either `git rebase --abort` or hard-align only after confirming no unique local work would be lost. Prefer using the remote commit hash as source of truth over forcing a duplicate push.93- If `git push` reports `remote rejected ... cannot lock ref ... is at <new> but expected <old>`, treat it as an ambiguous push outcome, not an automatic failure. Immediately verify with `git rev-parse HEAD` and `git ls-remote origin refs/heads/main`. If both hashes match, the push actually landed and no retry is needed.94- Keep final verification anchored to the four approval surfaces plus remote hash: GitHub label, approval marker, plan header, README row, and the pushed/observed remote commit hash. `origin/main == HEAD` is ideal, but in a dirty/concurrent checkout it is acceptable for `origin/main` to contain the verified approval-sync commit while local `HEAD` has unrelated divergence; report that distinction explicitly.9596## Example artifact updates97- `.planning/plan-approved/2269.md`98- `docs/plans/2026-04-15-issue-2269-openfoam-v2312-baseline-workflow-and-validation.md`99- `docs/plans/README.md`100101## Output expectation102After sync, local and remote state should agree that the issue is approved and ready for execution.