Frontmatter
| title | fix(ai): reject GitHub sync CLI off dev (#12780) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 8, 2026, 9:08 PM |
| updatedAt | Jun 8, 2026, 11:05 PM |
| closedAt | Jun 8, 2026, 11:05 PM |
| mergedAt | Jun 8, 2026, 11:05 PM |
| branches | dev ← codex/12780-sync-cli-dev-guard |
| url | https://github.com/neomjs/neo/pull/12781 |

PR Review Summary
Status: Approved
πͺ Strategic-Fit Decision
Per Β§9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Delivers #12780 β a fail-closed dev-branch preflight guard on the manual GitHub Workflow sync CLI, preventing an operator from running the content-mirror sync off the wrong branch (which would pollute
resources/content/*on a non-dev branch). Aligns with Β§critical_gates #3 (the data-sync pipeline is the dev-only exception). Pure testable helper, 7 specs green, CI 7/7. No blockers.
Opening: Clean safety guard β exactly the shape a manual destructive-write CLI should have. Fail-closed (refuses if it can't confirm the branch), a cross-checkout guard against context leakage, dev-only with a clear rejection message + remedy, and a pure helper fully unit-testable without the heavy AI SDK. Notes below; no blockers.
π§ Patch-Blind Premise Snapshot
- Inputs Read Before Patch: #12780 (close-target β "Reject syncGithubWorkflow CLI off dev"; labels bug/ai/build/model-experience) + #12696 (epic) / #12779 (related); the data-sync-pipeline-on-dev convention (Β§critical_gates #3); the guard helper + CLI wiring + spec. Checked out
d954b2832; ran the spec (7 passed). - Expected Solution Shape: a fail-closed preflight at the CLI invocation surface (before remote calls / writes / lease) that allows only
dev, rejects everything else (incl. detached HEAD / undetermined branch) with a clear message, as a pure unit-testable helper (no heavy-SDK import). Must NOT alterrunFullSync()behavior. - Patch Verdict: Matches.
buildSyncGithubWorkflowDevBranchGuard(delegate, getBranch)rejects if branch !== 'dev' (detached β '(detached)'), fail-closes if the branch can't be determined ("Refusing to sync without branch confirmation"), guards cross-checkout (top-level must match project root β "prevents context leakage"), and delegates only on dev; wired at the CLI surface withrunFullSync()unchanged.
πΈοΈ Context & Graph Linking
- Target Issue ID: Resolves #12780
- Related Graph Nodes: #12696 (v13 epic), #12779 (sync_all warm-path validation), #12693 (release-note cache fix).
π¬ Depth Floor
Challenge (non-blocking): the guard sits at the operator CLI invocation surface, not inside runFullSync() β so a future caller invoking runFullSync() directly (bypassing the CLI) wouldn't hit the dev-branch guard. That's the declared scope (the manual CLI is the human-error risk surface; the daemon runs the canonical sync from its dev context), and pushing the guard into runFullSync would couple it to the heavy SDK + defeat the pure-helper testability β so it's the right call. A one-line comment at runFullSync ("branch-guarded at the CLI surface only") would pre-empt a future direct-caller silently bypassing it. Non-blocking.
Rhetorical-Drift Audit: Pass. The rejection messages accurately describe the mechanism (the sync writes to resources/content/* β would pollute a non-dev branch) and the remedy (switch to dev / let the daemon run from its dev context). "Fail before remote calls, local writes, or heavy-maintenance lease acquisition" matches the CLI-surface wiring (the guard runs before the lease + the sync). No overshoot.
π― Close-Target Audit (Β§5.2)
-
Resolves #12780(newline-isolated); labels bug/ai/build/model-experience β notepic. βRelated: #12696(epic) /#12779correctly non-closing.
π§ͺ Test-Execution & Location Audit (Β§7.5)
- Checked out
d954b2832; rantest/playwright/unit/ai/scripts/maintenance/syncGithubWorkflow.spec.mjsβ 7 passed: delegates-on-dev; rejects feature / main / detached-HEAD; preserves root-mismatch rejections; wraps generic detector errors (fail-closed); wires the CLI guard before lease acquisition. - Canonical location:
test/playwright/unit/ai/scripts/maintenance/β correct.
Findings: Pass β comprehensive coverage of the happy path + every rejection path + the fail-closed branch + the CLI-wiring order.
π CI / Security Checks Audit (Β§7.6)
- CI 7/7 green on
d954b2832(0 pending, 0 failed) β verified at intake, re-confirmed at post.
N/A Audits β π‘ π π
N/A: no openapi / MCP tool surface (π‘); no public/consumed code contract β an internal CLI guard (π); no new cross-skill convention (π).
π Evaluation Metrics
[ARCH_ALIGNMENT]: 95 β fail-closed pure-helper guard at the right surface, aligned with Β§critical_gates #3 (data-sync-on-dev); 5 deducted for therunFullSync-direct-caller bypass note (non-blocking).[CONTENT_COMPLETENESS]: 95 β thorough JSDoc + clear rejection messages with WHY + remedy; 5 deducted for the missingrunFullSync"guarded-at-CLI-only" note.[EXECUTION_QUALITY]: 95 β correct fail-closed logic, 7 specs green (all paths), CI 7/7; 5 deducted for the same bypass-note nit.[PRODUCTIVITY]: 100 β delivers #12780 fully: reject / delegate / fail-closed / cross-checkout, all tested.[IMPACT]: 70 β prevents a real operator-error class (running the content-mirror sync off the wrong branch β pollutingresources/content/*); a meaningful safety guard on a destructive-write CLI.[COMPLEXITY]: 35 β Low-moderate: a pure branch-detect + guard helper + the CLI wiring; the cross-checkout top-level check is the one subtle bit.[EFFORT_PROFILE]: Quick Win β a small, well-tested safety guard with high error-prevention ROI.
[KB_GAP] / [TOOLING_GAP]: none.
π Required Actions
No required actions β eligible for human merge. (The runFullSync "guarded-at-CLI-only" comment is a non-blocking nit.)
Closing: A clean, fail-closed safety guard that stops an operator from running the content-mirror sync off the wrong branch β pure, testable, at the right surface, with clear rejection messages, aligned with the data-sync-pipeline-on-dev convention. 7 specs green (every path), CI 7/7. Eligible for human merge (@tobiu's gate).
Authored by GPT-5 (Codex Desktop). Session e8f07ef9-ef7e-4815-8ff4-7abe13720621.
Resolves #12780 Related: #12696 Related: #12779
Adds a dev-branch preflight guard to the manual GitHub Workflow sync CLI so
npm run ai:sync-github-workflownow fails before remote calls, local writes, or heavy-maintenance lease acquisition when invoked outsidedev.Evidence: L2 (pure branch-guard unit tests + source-order wiring check + existing script/config contract tests) -> L2 required (reject/delegate behavior without running a real full sync). No residuals.
Deltas from ticket
syncGithubWorkflowBranchGuard.mjshelper so reject/delegate behavior is unit-testable without importing the heavy AI services SDK.GH_SyncService.runFullSync()unchanged; the guard remains at the operator CLI invocation surface, matching the existing MCP tool-boundary precedent from#11145.syncGithubWorkflow.mjs, preserving CLI execution while making the entry point source-checkable.GH_SyncService.runFullSync()delegate.Decision Record impact: none. This is a branch-safety parity fix for an existing manual CLI surface.
Test Evidence
npm run test-unit -- test/playwright/unit/ai/scripts/maintenance/syncGithubWorkflow.spec.mjs test/playwright/unit/ai/scripts/maintenance/manualHeavyMaintenanceScriptLeaseAdoption.spec.mjs test/playwright/unit/ai/mcp/server/github-workflow/ConfigCompleteness.spec.mjs-> 28 passed.git diff --check-> passed.git diff --cached --check-> passed.git log origin/dev..HEAD --format='%h%x09%s%n%b'contains only commitd954b2832 fix(ai): reject GitHub sync CLI off dev (#12780).git merge-base HEAD origin/devmatchedgit rev-parse origin/dev; branch was 1 ahead / 0 behind before push.Post-Merge Validation
dev, runnpm run ai:sync-github-workflow -- --verboseonly when an operator intentionally wants a full GitHub Workflow sync; confirm the CLI enters the lease and sync path.node ai/scripts/maintenance/syncGithubWorkflow.mjsand confirm it exits withsyncGithubWorkflow REJECTEDbefore sync startup logs.Commit
d954b2832βfix(ai): reject GitHub sync CLI off dev (#12780)