Context
While validating the v13 sync_all cached-release warm path, I deliberately avoided running npm run ai:sync-github-workflow from the current feature branch. Source inspection showed a guard asymmetry:
ai/mcp/server/github-workflow/toolService.mjs:70 wraps the MCP sync_all handler with buildDevBranchGuard().
ai/mcp/server/github-workflow/toolService.mjs:81 rejects any branch other than dev before remote calls or local writes.
ai/scripts/maintenance/syncGithubWorkflow.mjs:46 enters the CLI wrapper directly, then calls GH_SyncService.runFullSync() under the heavy-maintenance lease at :58-:60.
package.json:67 exposes this wrapper as npm run ai:sync-github-workflow.
That CLI is intentionally the long-running twin of MCP sync_all, and its own JSDoc says both surfaces resolve to the same GH_SyncService.runFullSync() orchestration path. The MCP path is mechanically guarded against feature-branch pollution; the CLI path currently is not.
Live latest-open sweep: checked the latest 100 open issues on 2026-06-08 via GitHub API title/metadata scan. No focused open ticket for a syncGithubWorkflow CLI dev-branch guard was found.
Targeted live duplicate searches:
syncGithubWorkflow dev branch guard returned no equivalent open ticket; broad matches were unrelated open AiConfig tickets.
sync_all CLI feature branch returned no open equivalent.
sync_all dev branch constraint returned closed/documentation/MCP guard tickets, including #10830, #10835, and #11145.
Exact/local sweep: rg across resources/content/issues, resources/content/discussions, ai/scripts/maintenance, ai/mcp/server/github-workflow, and .agents/skills found the closed MCP guard (#11145/#11146), branch-pollution history (#10285/#11133), manual-script/heavy-lease history (#11503/#11507), and the current unguarded syncGithubWorkflow.mjs script. None is an open CLI branch-guard leaf.
Semantic duplicate sweep: used non-synthesis query_documents(type='ticket') because KB ask synthesis has been part of the v13 cost-safety incident surface. Results surfaced related historical issues (#10285, #11148, #10830, #10835, #11580, #11582), but no active CLI guard ticket.
The Problem
The branch-safety invariant for GitHub Workflow sync is currently split by invocation surface.
MCP sync_all rejects non-dev callers before it can write resources/content/**. The manual CLI wrapper exists specifically to run the same full sync outside the MCP timeout ceiling. It can also write the same generated content and metadata. If an agent or operator runs the CLI while parked on a feature branch, the same class of pollution that #11145 fixed for MCP can reappear through the CLI path.
This is not hypothetical as a workflow risk: #12779 validation needed the CLI-equivalent path, but the current branch was codex/12776-runtime-freshness-helper. Running the CLI directly would have been the wrong substrate shape. Discipline caught this instance; executable guard should catch future instances.
The Architectural Reality
- MCP
sync_all guard lives in ai/mcp/server/github-workflow/toolService.mjs as buildDevBranchGuard(delegate, getBranch).
- The CLI wrapper lives in
ai/scripts/maintenance/syncGithubWorkflow.mjs and imports through ai/services.mjs, preserving the service boundary.
- The CLI wrapper already has a whole-run heavy-maintenance lease; that prevents maintenance collisions, but it does not verify the Git branch.
GH_SyncService.runFullSync() writes regeneratable GitHub Workflow mirrors and metadata; branch choice matters even when the data is cache-like.
- Existing branch discipline also has pre-push protection for accidental chore-sync commits, but pre-push is a late failure. The CLI should reject before remote calls and local writes, matching the MCP guard's fail-fast shape.
The Fix
Add a dev-branch preflight guard to ai/scripts/maintenance/syncGithubWorkflow.mjs before it acquires the heavy-maintenance lease and before it calls GH_SyncService.runFullSync().
Expected implementation shape:
- Use a small local helper or reuse/extract an existing safe helper if that keeps boundaries clean.
- Check the repository top-level and current branch from the script's project context.
- Reject detached or non-
dev heads with a clear syncGithubWorkflow REJECTED message.
- Exit non-zero before remote GitHub GraphQL calls and before local writes.
- Keep daemon/release-owned paths untouched. This ticket is only for the operator/agent CLI wrapper.
- Add focused unit coverage or a low-risk script test that proves non-
dev rejection and dev delegation without running the real sync.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
npm run ai:sync-github-workflow / syncGithubWorkflow.mjs |
This ticket + MCP sync_all guard precedent |
Reject before sync when current branch is not dev |
Print actionable remediation and exit non-zero |
Script JSDoc / PR body |
Unit or subprocess test with injected/faked branch state |
MCP sync_all |
#11145 / toolService.mjs |
Remains guarded at MCP tool boundary |
No change |
Existing OpenAPI/tool docs |
Existing tests stay green |
GH_SyncService.runFullSync() |
GitHub Workflow service boundary |
Remains a reusable service; callers decide whether their invocation surface needs branch gating |
No service-level branch guard added |
Source JSDoc |
No daemon/release regression |
Decision Record impact
none. This is a branch-safety parity fix for an existing manual CLI surface. It does not change AiConfig, provider routing, or release-note narrative policy.
Acceptance Criteria
Out of Scope
- Running the full GitHub Workflow sync as part of the implementation.
- Changing
GH_SyncService.runFullSync() itself.
- Changing daemon/release publish paths that may call sync code from controlled
dev contexts.
- Reworking generated-content pre-push/chore-sync hooks.
- Reopening #11145's MCP guard design.
Avoided Traps
- Treating the heavy-maintenance lease as a branch-safety guard. It serializes heavy work; it does not protect feature branches from generated content.
- Adding a guard inside
GH_SyncService.runFullSync() itself. That would risk blocking legitimate controlled service callers; the invocation surface is the right substrate.
- Relying on documentation or operator discipline. The prior MCP history proved description-as-policy was insufficient.
- Waiting until pre-push to catch generated content. This should fail before remote calls and local writes.
Related
Parent: #12696
Related: #12779, #11145, #10830, #10835, #10285, #11133, #11503, #11507
Origin Session ID: e8f07ef9-ef7e-4815-8ff4-7abe13720621
Retrieval Hint: "syncGithubWorkflow CLI dev branch guard feature branch generated content sync_all parity"
Context
While validating the v13
sync_allcached-release warm path, I deliberately avoided runningnpm run ai:sync-github-workflowfrom the current feature branch. Source inspection showed a guard asymmetry:ai/mcp/server/github-workflow/toolService.mjs:70wraps the MCPsync_allhandler withbuildDevBranchGuard().ai/mcp/server/github-workflow/toolService.mjs:81rejects any branch other thandevbefore remote calls or local writes.ai/scripts/maintenance/syncGithubWorkflow.mjs:46enters the CLI wrapper directly, then callsGH_SyncService.runFullSync()under the heavy-maintenance lease at:58-:60.package.json:67exposes this wrapper asnpm run ai:sync-github-workflow.That CLI is intentionally the long-running twin of MCP
sync_all, and its own JSDoc says both surfaces resolve to the sameGH_SyncService.runFullSync()orchestration path. The MCP path is mechanically guarded against feature-branch pollution; the CLI path currently is not.Live latest-open sweep: checked the latest 100 open issues on 2026-06-08 via GitHub API title/metadata scan. No focused open ticket for a
syncGithubWorkflowCLI dev-branch guard was found.Targeted live duplicate searches:
syncGithubWorkflow dev branch guardreturned no equivalent open ticket; broad matches were unrelated open AiConfig tickets.sync_all CLI feature branchreturned no open equivalent.sync_all dev branch constraintreturned closed/documentation/MCP guard tickets, including #10830, #10835, and #11145.Exact/local sweep:
rgacrossresources/content/issues,resources/content/discussions,ai/scripts/maintenance,ai/mcp/server/github-workflow, and.agents/skillsfound the closed MCP guard (#11145/#11146), branch-pollution history (#10285/#11133), manual-script/heavy-lease history (#11503/#11507), and the current unguardedsyncGithubWorkflow.mjsscript. None is an open CLI branch-guard leaf.Semantic duplicate sweep: used non-synthesis
query_documents(type='ticket')because KB ask synthesis has been part of the v13 cost-safety incident surface. Results surfaced related historical issues (#10285, #11148, #10830, #10835, #11580, #11582), but no active CLI guard ticket.The Problem
The branch-safety invariant for GitHub Workflow sync is currently split by invocation surface.
MCP
sync_allrejects non-devcallers before it can writeresources/content/**. The manual CLI wrapper exists specifically to run the same full sync outside the MCP timeout ceiling. It can also write the same generated content and metadata. If an agent or operator runs the CLI while parked on a feature branch, the same class of pollution that #11145 fixed for MCP can reappear through the CLI path.This is not hypothetical as a workflow risk: #12779 validation needed the CLI-equivalent path, but the current branch was
codex/12776-runtime-freshness-helper. Running the CLI directly would have been the wrong substrate shape. Discipline caught this instance; executable guard should catch future instances.The Architectural Reality
sync_allguard lives inai/mcp/server/github-workflow/toolService.mjsasbuildDevBranchGuard(delegate, getBranch).ai/scripts/maintenance/syncGithubWorkflow.mjsand imports throughai/services.mjs, preserving the service boundary.GH_SyncService.runFullSync()writes regeneratable GitHub Workflow mirrors and metadata; branch choice matters even when the data is cache-like.The Fix
Add a dev-branch preflight guard to
ai/scripts/maintenance/syncGithubWorkflow.mjsbefore it acquires the heavy-maintenance lease and before it callsGH_SyncService.runFullSync().Expected implementation shape:
devheads with a clearsyncGithubWorkflow REJECTEDmessage.devrejection anddevdelegation without running the real sync.Contract Ledger Matrix
npm run ai:sync-github-workflow/syncGithubWorkflow.mjssync_allguard precedentdevsync_alltoolService.mjsGH_SyncService.runFullSync()Decision Record impact
none. This is a branch-safety parity fix for an existing manual CLI surface. It does not change AiConfig, provider routing, or release-note narrative policy.
Acceptance Criteria
syncGithubWorkflow.mjschecks the active branch before acquiring the heavy-maintenance lease or invokingGH_SyncService.runFullSync().dev, detached, or branch-detection-failure states fail before remote calls and local writes with an actionablesyncGithubWorkflow REJECTEDmessage.devpath still delegates toGH_SyncService.runFullSync()under the existing heavy-maintenance lease.sync_allbehavior remains unchanged.Out of Scope
GH_SyncService.runFullSync()itself.devcontexts.Avoided Traps
GH_SyncService.runFullSync()itself. That would risk blocking legitimate controlled service callers; the invocation surface is the right substrate.Related
Parent: #12696 Related: #12779, #11145, #10830, #10835,
#10285,#11133,#11503,#11507Origin Session ID: e8f07ef9-ef7e-4815-8ff4-7abe13720621 Retrieval Hint: "syncGithubWorkflow CLI dev branch guard feature branch generated content sync_all parity"