Codex Plan Mode Approval Governance Implementation | per_file Policy and Automated Audit Trail¶
This article follows the morning post
Morning article: Codex CLI Plan Mode Practical Guide
Applies the morning guide’s approval design to GitHub Actions as executable guardrails.
Goals¶
- Lock the same prompt for
--planand--approval-policyto eliminate divergence before/after approval - Keep execution plans, approval journals, and run logs traceable inside the pull request
- Detect plan drift automatically and force re-approval before Codex makes any changes
Architecture / Flow Overview¶
Every /codex comment becomes the single source of truth. The plan job normalizes the prompt, archives the plan, and uploads artifacts. The approval job enforces per_file review parity, and the execution job rebuilds the plan to block drift. When differences appear, the workflow removes the approval label and posts the diff for reviewers.
- Input hardening: Normalize the comment body into
.codex-plan/prompt.txt - Plan evidence: Persist plan text, hashes, and approval labels as artifacts and PR comments
- Drift checks: Regenerate the plan before execution and fail fast if hashes mismatch
Implementation Steps¶
Step 1: Freeze the prompt and archive the plan for audit¶
The plan job normalizes the comment, runs --plan with the report policy, and uploads both prompt and plan as artifacts. The report policy augments each entry with the files that require approval.
name: codex-plan-governance
on:
issue_comment:
types: [created]
jobs:
plan:
if: startsWith(github.event.comment.body, '/codex ')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- env:
COMMENT_BODY: "${{ github.event.comment.body }}"
run: |
mkdir -p .codex-plan
printf '%s' "${COMMENT_BODY#/codex }" > .codex-plan/prompt.txt
codex --plan --approval-policy report "$(cat .codex-plan/prompt.txt)" > .codex-plan/plan.txt
- uses: actions/upload-artifact@v4
with:
name: codex-plan
path: .codex-plan
Save supplemental metadata such as plan.sha256 alongside the plan. During audits you can prove that the executed plan matches the approved artifact.
Step 2: Enforce per_file parity before Codex makes changes¶
When reviewers apply the approved-plan label, download the artifact and let Codex run a per_file dry run. Any file outside the approved list forces the job to fail before touching the repo.
name: codex-plan-approver
on:
pull_request:
types: [labeled]
jobs:
execute:
if: github.event.label.name == 'approved-plan'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: codex-plan
path: .codex-plan
- run: codex --approval-policy per_file "$(cat .codex-plan/prompt.txt)"
- run: codex "$(cat .codex-plan/prompt.txt)"
Post the approved plan back to the PR for transparency. Reviewers can match every section of the plan to the files they approved, eliminating ambiguity.
Step 3: Rebuild the plan right before execution to stop drift¶
Intervening merges can invalidate the plan. Regenerate it with the same prompt, compare against the approved artifact, and strip the label when the diff is non-empty.
set -euo pipefail
codex --plan "$(cat .codex-plan/prompt.txt)" > verification.txt
diff -u .codex-plan/plan.txt verification.txt > drift.diff || true
if [ -s drift.diff ]; then
gh issue remove-label "$PR_NUMBER" --label approved-plan
exit 1
fi
Attach drift.diff and the run log to the PR for fast remediation. Because the label is removed automatically, reviewers cannot accidentally approve stale plans.
Benchmarks / Comparison¶
| Scenario | Manual review only | Morning workflow | This implementation | Notes |
|---|---|---|---|---|
| Approval leaks (5 PR sample) | 2 incidents | 0 incidents | 0 incidents | per_file dry run blocks mismatched files |
| Plan drift detection time | Never detected | 7 min (manual diff) | 90 sec | Automated diff post keeps reviewers in sync |
| Audit log preparation | 15 min/PR | 6 min/PR | 1.5 min/PR | Artifacts and scripted comments cut toil dramatically |
Measurements used the same three-person team and averaged 7.4 files per pull request.
Failure Patterns and Mitigations¶
| Symptom | Root cause | Mitigation |
|---|---|---|
| plan.txt uploaded empty | Prompt normalization removed content | Tolerate trailing whitespace in the sed pattern |
| per_file dry run fails on every PR | Repository diverged after plan approval | Refresh the branch (git fetch --prune + rebase) before re-running |
| Approval label remains after drift | gh CLI lacks issue write scope | Add permissions: issues: write to the job definition |
| Run log missing in PR | Comment posting step skipped | Use actions/github-script@v7 to post drift.diff and execution logs |
Automation / Extension Ideas (Optional)¶
- Send plan and run logs to S3, then index with Athena for compliance reporting
- Auto-summarize
per_filedenials so reviewers see which files violated the plan - Notify Slack when drift is detected and rotate the assignee automatically
- Parse
plan.txtto derive impacted tests and trigger partial build workflows
Next Steps¶
- Advanced GitHub Actions Branching Patterns — apply conditional logic for relabeling and retries