---
id: 12048
title: bootstrapWorktree duplicates initServerConfigs without materialize-on-copy
state: CLOSED
labels:
  - bug
  - ai
  - refactoring
  - architecture
assignees: []
createdAt: '2026-05-26T20:25:36Z'
updatedAt: '2026-05-26T20:34:43Z'
githubUrl: 'https://github.com/neomjs/neo/issues/12048'
author: neo-opus-ada
commentsCount: 1
parentIssue: null
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-05-26T20:34:43Z'
---
# bootstrapWorktree duplicates initServerConfigs without materialize-on-copy

## Context

Surfaced 2026-05-26 while debugging the unitTestMode collision in [PR #12044](https://github.com/neomjs/neo/pull/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`](https://github.com/neomjs/neo/blob/dev/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`](https://github.com/neomjs/neo/blob/dev/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:

```javascript
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

- [`ai/scripts/setup/initServerConfigs.mjs:129-133`](https://github.com/neomjs/neo/blob/dev/ai/scripts/setup/initServerConfigs.mjs#L129-L133) — `materializeServerConfigTemplate` (the canonical rewrite logic).
- [`ai/scripts/setup/initServerConfigs.mjs:173-231`](https://github.com/neomjs/neo/blob/dev/ai/scripts/setup/initServerConfigs.mjs#L173-L231) — `initConfigs` (per-server bootstrap with template-copy + materialize on missing config).
- [`ai/scripts/setup/initServerConfigs.mjs:252-292`](https://github.com/neomjs/neo/blob/dev/ai/scripts/setup/initServerConfigs.mjs#L252-L292) — `initTier1Config` (Tier-1 overlay bootstrap from `config.template.mjs`).
- [`ai/scripts/migrations/bootstrapWorktree.mjs:113-118`](https://github.com/neomjs/neo/blob/dev/ai/scripts/migrations/bootstrapWorktree.mjs#L113-L118) — `BOOTSTRAP_CONFIGS` list.
- [`ai/scripts/migrations/bootstrapWorktree.mjs:226-253`](https://github.com/neomjs/neo/blob/dev/ai/scripts/migrations/bootstrapWorktree.mjs#L226-L253) — copy-from-canonical loop.

## 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](https://github.com/neomjs/neo/issues/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](https://github.com/neomjs/neo/issues/12047) — the materialization-migration concern for existing canonical checkouts.
- Empirical anchor: [PR #12044](https://github.com/neomjs/neo/pull/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`](https://github.com/neomjs/neo) (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.

## Timeline

- 2026-05-26T20:25:38Z @neo-opus-ada added the `bug` label
- 2026-05-26T20:25:38Z @neo-opus-ada added the `ai` label
- 2026-05-26T20:25:38Z @neo-opus-ada added the `refactoring` label
- 2026-05-26T20:25:38Z @neo-opus-ada added the `architecture` label
### @neo-opus-ada - 2026-05-26T20:33:49Z

**Input from claude-opus-4-7 (Claude Code):**

> ❋ ## Closing — converted to /ideation-sandbox Discussion
> 
> Operator feedback: this ticket was wrong-shape. Tickets in the actionable tracker should commit to one prescription; three options with a hedged "my read" means the design space hasn't converged yet — that's the /ideation-sandbox signal, not the ticket-create signal.
> 
> Converted to [Discussion #12049](https://github.com/neomjs/neo/discussions/12049). The Discussion includes:
> 
> - §5.1 Double Diamond divergence matrix (5 columns, ≥2 alternatives + recommendation, falsifying source per rejected option).
> - §5.1.1 Reflective Pause documentation (friction-driven origin → root-cause sweep across symptom / direct cause / architectural cause / workflow-design cause).
> - Four Open Questions with `[OQ_RESOLUTION_PENDING]` tags.
> - Explicit graduation criteria.
> 
> The Discussion's primary OQ is the substrate-workflow question that wasn't actionable as a ticket: **operator-edit propagation vs fresh-materialized-isolation semantics for worktrees**. Once cross-family peer convergence settles that, a clean single-prescription ticket lands.
> 
> This ticket is closed; no work lost. The Related links in #12049 carry the full context.
> 
> — @neo-opus-ada

- 2026-05-26T20:34:44Z @neo-opus-ada closed this issue
- 2026-05-26T21:11:02Z @neo-opus-ada cross-referenced by #12051

