LearnNewsExamplesServices
Frontmatter
id12048
titlebootstrapWorktree duplicates initServerConfigs without materialize-on-copy
stateClosed
labels
bugairefactoringarchitecture
assignees[]
createdAtMay 26, 2026, 10:25 PM
updatedAtMay 26, 2026, 10:34 PM
githubUrlhttps://github.com/neomjs/neo/issues/12048
authorneo-opus-ada
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 26, 2026, 10:34 PM

bootstrapWorktree duplicates initServerConfigs without materialize-on-copy

Closed v13.0.0/archive-v13-0-0-chunk-14 bugairefactoringarchitecture
neo-opus-ada
neo-opus-ada commented on May 26, 2026, 10:25 PM

Context

Surfaced 2026-05-26 while debugging the unitTestMode collision in PR #12044. Operator pointed at the substrate duplication between two scripts that both manage gitignored config substrate but with different semantics:

  • ai/scripts/setup/initServerConfigs.mjs — runs at npm prepare. For missing per-server config.mjs, clones from config.template.mjs AND applies materializeServerConfigTemplate to rewrite import AiConfig from '../../../config.template.mjs''../../../config.mjs' (operator overlay).
  • ai/scripts/migrations/bootstrapWorktree.mjs — runs after git worktree add. Copies the canonical checkout's per-server config.mjs verbatim into the worktree. No materialize step. Also doesn't copy ai/config.mjs (Tier-1 operator overlay).

The Problem

Three distinct issues from the same substrate gap:

1. No materialize-on-copy in bootstrapWorktree

bootstrapWorktree.mjs:247 does await fs.copyFile(src, dst) — raw byte copy from canonical to worktree. If the canonical checkout's per-server config.mjs is un-materialized (predates the materialize logic, or was never re-run with --migrate-config), the worktree inherits the un-materialized state.

This propagates the import-path bug from canonical to every spawned worktree. With Agent(isolation: "worktree") being the standard Claude Code workflow, this affects most agent-spawned work.

2. Tier-1 ai/config.mjs not bootstrapped

BOOTSTRAP_CONFIGS at bootstrapWorktree.mjs:113-118 lists only the 4 per-server configs:

export const BOOTSTRAP_CONFIGS = [
    'ai/mcp/server/github-workflow/config.mjs',
    'ai/mcp/server/knowledge-base/config.mjs',
    'ai/mcp/server/memory-core/config.mjs',
    'ai/mcp/server/neural-link/config.mjs'
];

The Tier-1 ai/config.mjs (canonical operator overlay) is missing. If a worktree's per-server config.mjs imports '../../../config.mjs' (post-materialize), the import will fail because Tier-1 doesn't exist in the worktree. This works today only because most per-server configs are un-materialized (importing config.template.mjs directly) — i.e., bug #1 masks bug #2.

3. Duplication of substrate responsibility

Both scripts manage gitignored config.mjs files. They diverged because:

  • initServerConfigs is a generic "set up from template" tool (npm-prepare cadence).
  • bootstrapWorktree is a "hydrate gitignored from canonical" tool (worktree-add cadence).

But the worktree case is structurally equivalent to "fresh clone" once you accept that the worktree should generate its own materialized configs from template. The right abstraction is to have bootstrapWorktree delegate to initServerConfigs rather than implement its own copy logic.

The Architectural Reality

The Fix

Three viable shapes (PR author picks); not mutually exclusive:

Option A — bootstrapWorktree delegates to initServerConfigs

Replace the copy-from-canonical loop with a call to initConfigs() + initTier1Config() (export them as a reusable bootstrap entry point if not already). The worktree gets fresh, materialized configs from template. Operator-edited content (tenantRepos[], etc.) does NOT propagate — operator has to re-edit per worktree. Pro: correct materialization. Con: loses operator-edit propagation, which is the current bootstrapWorktree value-add.

Option B — bootstrapWorktree adds a materialize-on-copy step

After fs.copyFile, run the canonical's content through materializeServerConfigTemplate (read, apply, write). Operator-edited content propagates AND the import-path rewrite always lands. Pro: preserves current semantic + fixes the bug. Con: the materialize logic now lives in two places (drift risk).

Option C — bootstrapWorktree adds Tier-1 to BOOTSTRAP_CONFIGS + applies materialize

Combines option B with adding 'ai/config.mjs' to the bootstrap list so post-materialize imports resolve. Worktree carries a copy of canonical's Tier-1 overlay. Pro: complete; matches current bootstrap-as-hydrate semantic with the materialize correctness. Con: even more substrate coupling between the two scripts.

My read: Option B is the immediate fix (1-line behavior change + minimal blast radius). Long-term, Option A is the architecturally right shape (single substrate; bootstrapWorktree becomes a thin worktree-aware wrapper around initServerConfigs). Recommend filing Option A as a follow-up after Option B lands.

Decision Record impact

amends the operator-overlay bootstrap contract documented across the two scripts. ADR successor-risk audit needed before Option A (might affect operator's expected worktree-edit-propagation behavior).

Acceptance Criteria

  • bootstrapWorktree.mjs no longer copies un-materialized per-server config.mjs into worktrees — whichever option lands ensures the worktree's copy has the correct import path.
  • Tier-1 ai/config.mjs is bootstrapped into worktrees (either via Option C copy OR Option A's initTier1Config invocation).
  • Existing fresh-clone tests still pass (initServerConfigs behavior unchanged for the non-worktree case).
  • Test: bootstrap a fresh worktree from a canonical checkout whose per-server config.mjs imports config.template.mjs; verify the worktree's copy imports '../../../config.mjs' (materialize applied OR delegated).
  • Test: bootstrap a fresh worktree from a canonical checkout that has Tier-1 ai/config.mjs; verify the worktree's ai/config.mjs exists post-bootstrap.
  • Docs note in bootstrapWorktree.mjs JSDoc reflecting the chosen approach.

Out of Scope

  • The materialization-migration concern for existing un-materialized canonical checkouts (covered by #12047).
  • Renaming / restructuring the BOOTSTRAP_CONFIGS list semantics beyond adding Tier-1.

Avoided Traps

  • Option-A-first: tempting because it's the right long-term shape, but the operator-edit-propagation semantic is currently load-bearing for some workflows (worktree gets canonical's tenantRepos[] so the agent can resolve tenant configs). Option B preserves that semantic while fixing the bug. Architectural cleanup comes after the immediate fix lands.
  • Treating this as a worktree-only concern: the materialization-state of canonical's config.mjs is the actual variable. If canonical is materialized correctly, the current copy works. If not, the gap propagates. Fixing materialize-on-copy means worktrees always work regardless of canonical state.

Related

  • Sibling: #12047 — the materialization-migration concern for existing canonical checkouts.
  • Empirical anchor: PR #12044 cycle-2 → cycle-2.1 simplification. Manual import-path fix on operator-local per-server config.mjs revealed both: the un-materialized state AND the bootstrapWorktree gap.
  • Memory: reference_bootstrap_worktree_for_configs (Claude Code agent memory): "Restoration fallback: cp config.template.mjs config.mjs per missing server. Empirical 2026-05-26" — flagged as a fallback because the canonical bootstrap path itself has the materialization gap.
  • Empirical session: first real-world cloud Agent OS deployment, 2026-05-26.