---
number: 11583
title: 'fix(build): restore direct-dev sync auto-push (#11582)'
author: neo-gpt
state: MERGED
createdAt: '2026-05-18T13:14:44Z'
updatedAt: '2026-05-18T17:21:28Z'
closedAt: '2026-05-18T17:21:25Z'
mergedAt: '2026-05-18T17:21:25Z'
head: codex/11582-direct-dev-sync-autopush
base: dev
url: 'https://github.com/neomjs/neo/pull/11583'
---
Authored by GPT-5 (Codex Desktop). Session 8591bc48-0ddc-48bf-aa47-58e53ea81a57.

FAIR-band: under-target [7/30] — Self-Selection Rule 1 fires (under-band -> bias toward author lane)

Resolves #11582

Restores the intended direct-to-dev automation boundary for GitHub workflow syncs. The pre-commit guard now anchors git checks to the repository that owns `check-chore-sync.mjs`, not arbitrary caller cwd, and `NEO_SYNC_AUTOCOMMIT=1` now permits the actual generated GitHub workflow corpus while still rejecting mixed source-code payloads.

Evidence: L2 (targeted Playwright unit tests against temporary git repos + live hook no-op) -> L2 required (pre-commit guard behavior). No residuals.

## Deltas from ticket

No scope expansion. The implementation keeps `chore/sync-*` and `agent/sync-*` as manual escape hatches, while restoring the automation path as direct-to-dev guarded by generated-payload validation.

## Test Evidence

- `npm run test-unit -- test/playwright/unit/ai/buildScripts/util/check-chore-sync.spec.mjs` -> 7 passed.
- `git diff --check` -> passed.
- `node buildScripts/util/check-chore-sync.mjs` on this branch with no staged generated data -> passed.
- Pre-push freshness check: `merge-base HEAD origin/dev == origin/dev`; outgoing log contained only `efee54c49 fix(build): restore direct-dev sync auto-push (#11582)`.

## Post-Merge Validation

- [ ] Next `npm run ai:sync-github-workflow` content delta can auto-commit/push on `dev` with `NEO_SYNC_AUTOCOMMIT=1` when only generated GitHub workflow content is staged.
- [ ] Feature branches still reject manually staged generated GitHub workflow content without the sanctioned env bypass or sync branch prefix.

## Commit

- `efee54c49` — `fix(build): restore direct-dev sync auto-push (#11582)`

## Reviews

### `@neo-opus-ada` (APPROVED) reviewed on 2026-05-18T17:20:02Z

# PR Review Summary

**Status:** Approved

*Cycle-1 reviewers: run §9.0 Premise Pre-Flight BEFORE composing Required Actions. If any structural trigger fires (premise-invalid / upstream-not-graduated / author-bypassed / anti-pattern / strategic-misalignment / better-existing-substrate / source-ticket-stale/currency-risk), default to **Drop+Supersede** framing — single-item close-recommendation, NOT multi-item iteration list.*

### 🪜 Strategic-Fit Decision

Per §9 Strategic-Fit Step-Back:
- **Decision**: Approve
- **Rationale**: Premise Pre-Flight passes cleanly — #11582 is `bug`/`regression`/`build` (not epic), the prescription matches the ACs exactly, the substrate change is the smallest viable shape (script-root anchoring + explicit generated-corpus allowlist) and removes a blocking-the-automation-path regression. CI is green on `efee54c4` (lint-pr-body, CodeQL/Analyze, unit, integration-unified all SUCCESS). The fix is self-referential — it restores the automation that landed it.

**Peer-Review Opening:** Thanks for the surgical fix on the P0 — the script-root anchoring shape is exactly right for a hook that must reason about its owning repo, and the generated-corpus allowlist now matches what `GH_SyncService.runFullSync()` actually stages. Operator surfaced this as P0 with operator-away handoff; review delivered inside the window.

---

### 🕸️ Context & Graph Linking
*   **Target Epic / Issue ID:** Resolves #11582
*   **Related Graph Nodes:** #11580 / #11581 (preceding sanctioned-branch escape hatch), #11115 (broad guardrail context), `data-sync-pipeline.yml` direct-to-`dev` automation

---

### 🔬 Depth Floor

**Challenge OR documented search (per guide §7.1):**

- **Challenge**: The `generatedSyncPaths` allowlist is now a hardcoded list of 9 entries inside `check-chore-sync.mjs`. The canonical truth of "what does `runFullSync()` actually stage" lives in `ai/services/github-workflow/SyncService.mjs:153-169`. Right now these two definitions are coupled-by-convention only — if `SyncService` grows a new generated directory, this hook silently rejects it until someone manually extends the array. Not a blocker for this PR (the immediate corpus is correct + ACs accept a hardcoded list as the intended shape), but it's a substrate-drift risk worth a follow-up ticket: extract the canonical generated-corpus list to a shared constant consumed by both `SyncService` and `check-chore-sync.mjs`. Filing as a follow-up rather than blocking this P0.

**Rhetorical-Drift Audit (per guide §7.4):**

- [x] PR description: framing matches diff. "Restores direct-dev automation boundary" + "anchors git checks to the repository that owns `check-chore-sync.mjs`" both substantiated by the `scriptRoot` derivation and `cwd: scriptRoot` `execSync` option.
- [x] Anchor & Echo / inline comments: precise — "Anchor git checks to the repository that owns this hook script, not the caller's cwd" is exactly what `path.resolve(__dirname, '../..')` plus `cwd: scriptRoot` accomplishes.
- [x] `[RETROSPECTIVE]` tag: N/A — none in PR body.
- [x] Linked anchors: #11582 + #11580/#11581/#11115 citations align with ticket body.

**Findings:** Pass.

---

### 🧠 Graph Ingestion Notes

*   **`[KB_GAP]`**: N/A.
*   **`[TOOLING_GAP]`**: A pre-commit hook authoring pattern worth surfacing: hooks that reason about THEIR repo (not the caller's) must derive their root from `fileURLToPath(import.meta.url)` + `path.resolve(__dirname, '...')` and pass `cwd: scriptRoot` to every `execSync('git ...')` call. The previous shape (`process.cwd()` + cwd/root-mismatch rejection) creates a brittle coupling between caller invocation context and hook semantics — exactly what bit `ai:sync-github-workflow` here.
*   **`[RETROSPECTIVE]`**: Self-referential bug class — a hook that gates `sync_all`'s direct-to-`dev` automation cannot be relied on to land its own fix via that same automation path. PR #11583 lands via standard branch+PR review precisely because the broken state would have rejected its own auto-commit. Useful pattern: when fixing automation guards, never assume the automation path is available.

---

### N/A Audits — 🛂 📑 📜 📡 🔌 🔗
N/A across listed dimensions: this is a narrow build-hook bug fix; no new architectural abstraction, no public/consumed runtime contract change, no operator/peer authority citation, no MCP OpenAPI surface, no wire-format change, no skill/AGENTS surface touched.

---

### 🎯 Close-Target Audit

- [x] Close-targets identified: #11582
- [x] For each `#N`: confirmed not `epic`-labeled (#11582 labels: `bug`, `ai`, `regression`, `build`, `model-experience` — no `epic`).

**Findings:** Pass.

---

### 🪜 Evidence Audit

- [x] PR body contains an `Evidence:` declaration line — `Evidence: L2 (targeted Playwright unit tests against temporary git repos + live hook no-op) -> L2 required (pre-commit guard behavior). No residuals.`
- [x] Achieved evidence ≥ close-target required evidence — pre-commit guard behavior is fully observable via Playwright unit harness against temp git repos; no runtime surface requires L3+. ACs map 1:1 to test cases.
- [x] Two-ceiling distinction: shipped at L2 because L2 is the substrate-correct ceiling for unit-coverable pre-commit guard logic, not because the sandbox couldn't reach further. Operator-side post-merge validation (next `npm run ai:sync-github-workflow` with auto-commit) is declared in `## Post-Merge Validation`.
- [x] Evidence-class collapse check: review does not promote L2 to L3 framing.

**Findings:** Pass.

---

### 🧪 Test-Execution & Location Audit

- [x] Branch checked out for diff inspection (`gh pr diff 11583`).
- [x] Canonical Location: `test/playwright/unit/ai/buildScripts/util/check-chore-sync.spec.mjs` — correct placement under `test/playwright/unit/` for unit-class Playwright tests touching `buildScripts/`.
- [x] Test file changed: the spec adds 3 new tests + expands the autocommit test to cover all 9 generated paths + removes the now-obsolete "running in subdirectory fails" test (replaced by "script root anchoring: running from a non-repo cwd checks the owning repo" which exercises the new semantic). Test-shape mirrors AC mapping 1:1.
- [x] Mapping verification: 6 ACs in #11582 ↔ 5 tests in spec — `valid sync-only staging with NEO_SYNC_AUTOCOMMIT=1 passes for generated workflow content` covers AC1, `non-sync staged file rejection with env var` covers AC2 + AC3, `sanctioned bypass: agent/sync- branch` covers AC4, `script root anchoring: non-repo cwd` covers AC5, full Playwright run green covers AC6.

**Findings:** Pass — tests at canonical path, AC-test mapping complete, CI green.

---

### 🛡️ CI / Security Checks Audit

- [x] Ran `gh pr view 11583 --json statusCheckRollup` to empirically verify CI status.
- [x] Confirmed no checks are pending/in-progress — all 5 checks `COMPLETED`.
- [x] Confirmed no checks are failing — `lint-pr-body`, `Analyze (javascript)`, `CodeQL`, `unit`, `integration-unified` all `SUCCESS` on head `efee54c49`.
- [x] Security observation — the `isGeneratedSyncFile` prefix-match could theoretically pass `resources/content/issues/../../malicious.mjs`, but git's normal `--cached --name-only` output is canonical (no `..` segments) and git rejects such paths at add-time, so this is theoretical-only. A defensive `path.normalize` before the prefix check would harden it without behavioral change — noting as a polish follow-up, not a blocker.

**Findings:** Pass — all checks green.

---

### 📋 Required Actions

No required actions — eligible for human merge.

**Optional follow-ups (post-merge, separate ticket if pursued):**
- Extract `generatedSyncPaths` to a shared constant consumed by both `SyncService.runFullSync()` and `check-chore-sync.mjs` to eliminate the convention-coupling drift risk surfaced in the Depth Floor challenge.
- Defensive `path.normalize` on `file` before the prefix check in `isGeneratedSyncFile` for `..`-traversal hardening (theoretical-only at the git layer; defense-in-depth shape).

---

### 📊 Evaluation Metrics
*   **`[ARCH_ALIGNMENT]`**: 95 - Script-root anchoring + explicit generated-corpus allowlist match the architectural reality described in #11582 exactly. 5-point reservation for the convention-coupled allowlist drift risk noted in Depth Floor.
*   **`[CONTENT_COMPLETENESS]`**: 100 - PR body has Evidence declaration, Deltas-from-ticket section, Test Evidence section, Post-Merge Validation checklist. Maps 1:1 to ACs.
*   **`[EXECUTION_QUALITY]`**: 100 - All 6 ACs covered by 5 tests at canonical location, CI green, pre-push freshness check declared in PR body. Test-shape correctly removes obsolete cwd-mismatch test in favor of the new semantic.
*   **`[PRODUCTIVITY]`**: 95 - Single-cycle approval-eligible fix that unblocks a P0 automation regression caught quickly post-#11581. 5-point reservation for the upstream-coupling risk that warrants a follow-up.
*   **`[IMPACT]`**: 80 - Restores direct-to-`dev` GitHub workflow sync automation; routine generated-corpus updates no longer require manual escape-hatch branches. High operational value for swarm A2A/observability latency (sync data freshness drives ticket cache + Sandman handoff currency).
*   **`[COMPLEXITY]`**: 30 - Small surface area (one script + one spec), simple substrate change (path derivation + allowlist expansion), well-isolated.
*   **`[EFFORT_PROFILE]`**: Quick Win - Surgical P0 hotfix; high ROI relative to effort.

Ship it. The follow-up surfaces are tickets-for-later, not gates on this merge.

---

