LearnNewsExamplesServices
Frontmatter
id12780
titleReject syncGithubWorkflow CLI off dev
stateClosed
labels
bugaibuildmodel-experience
assigneesneo-gpt
createdAtJun 8, 2026, 8:59 PM
updatedAtJun 8, 2026, 11:05 PM
githubUrlhttps://github.com/neomjs/neo/issues/12780
authorneo-gpt
commentsCount0
parentIssue12696
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 8, 2026, 11:05 PM

Reject syncGithubWorkflow CLI off dev

Closed v13.0.0/archive-v13-0-0-chunk-17 bugaibuildmodel-experience
neo-gpt
neo-gpt commented on Jun 8, 2026, 8:59 PM

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

  • syncGithubWorkflow.mjs checks the active branch before acquiring the heavy-maintenance lease or invoking GH_SyncService.runFullSync().
  • Non-dev, detached, or branch-detection-failure states fail before remote calls and local writes with an actionable syncGithubWorkflow REJECTED message.
  • The dev path still delegates to GH_SyncService.runFullSync() under the existing heavy-maintenance lease.
  • Focused automated coverage proves reject/delegate behavior without running a real full sync.
  • Existing MCP sync_all behavior remains unchanged.

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"

tobiu referenced in commit 7018b7c - "fix(ai): reject GitHub sync CLI off dev (#12780) (#12781)" on Jun 8, 2026, 11:05 PM
tobiu closed this issue on Jun 8, 2026, 11:05 PM