LearnNewsExamplesServices
Frontmatter
id12147
titlebootstrapWorktree CLI mis-resolves projectRoot one level short
stateClosed
labels
bugaibuild
assigneesneo-opus-ada
createdAtMay 28, 2026, 9:47 PM
updatedAtMay 28, 2026, 10:14 PM
githubUrlhttps://github.com/neomjs/neo/issues/12147
authorneo-opus-ada
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 28, 2026, 10:14 PM

bootstrapWorktree CLI mis-resolves projectRoot one level short

neo-opus-ada
neo-opus-ada commented on May 28, 2026, 9:47 PM

Context

Surfaced during #12145 worktree setup: running node ai/scripts/migrations/bootstrapWorktree.mjs left the worktree's config.mjs files missing (forcing a manual template-copy fallback to run unit tests) and created a stray ai/ai/ directory containing exactly the BOOTSTRAP_CONFIGS set.

The Problem

The CLI entry (isMain block) computes the repo root one directory-level short:

const projectRoot = path.resolve(__dirname, '..', '..'); // ai/scripts/ → ai/ → root

The script lives at ai/scripts/migrations/bootstrapWorktree.mjs (3 levels deep), so __dirname is <root>/ai/scripts/migrations. Two .. climbs only to <root>/ai, not the repo root. The comment ("ai/scripts/ → ai/ → root") is stale — it describes the pre-migrations/ location; the file was relocated into migrations/ without bumping the .. count. The config copy then does path.join('<root>/ai', 'ai/config.mjs') → writes to <root>/ai/ai/config.mjs, so the real ai/config.mjs never lands and every consumer that imports ai/services.mjs hits ERR_MODULE_NOT_FOUND.

The Architectural Reality

  • ai/scripts/migrations/bootstrapWorktree.mjs:901 — the buggy path.resolve(__dirname, '..', '..').
  • The exported bootstrapWorktree({projectRoot}) takes projectRoot as an argument, so the unit spec injects it correctly — the CLI-only inline computation is the untested glue. test/playwright/unit/ai/scripts/migrations/bootstrapWorktree.spec.mjs's JSDoc explicitly states the CLI path is "a thin wrapper ... not exercised here," which is how the off-by-one escaped coverage (same escape class as #12042).

The Fix

  1. Extract the CLI root computation to an exported, testable helper resolveCliProjectRoot(moduleDir) returning path.resolve(moduleDir, '..', '..', '..') (migrations/scripts/ai/ → root), with a corrected comment.
  2. Use it in the isMain block: const projectRoot = resolveCliProjectRoot(__dirname);.
  3. Add a spec case asserting it resolves <root>/ai/scripts/migrations<root> (with a regression note that the prior 2-level resolve mislanded at <root>/ai).

Acceptance Criteria

  • AC1: resolveCliProjectRoot('<x>/ai/scripts/migrations') returns <x> (repo root), unit-tested.
  • AC2: the isMain CLI path uses the helper; no inline 2-level path.resolve(__dirname, '..', '..') remains.
  • AC3: the stale comment is corrected.
  • AC4: the bootstrapWorktree spec passes.

Out of Scope

  • The build-all step's separate failure modes (heavy full build) — orthogonal to the path bug.
  • A broader CLI-path integration test — the helper unit test covers the escape class.

Related

  • Surfaced during #12145 (pull-mode tenantRepos tiered resolver) worktree setup.
  • Bootstrap feature origin: #10095; the relocation into migrations/ likely introduced the regression.

Decision Record impact

none — localized bug fix.

Handoff Retrieval Hints

  • Retrieval Hint: "bootstrapWorktree projectRoot off-by-one ai/ai stray configs"
tobiu referenced in commit 147dc26 - "fix(build): bootstrapWorktree CLI resolves projectRoot to repo root, not /ai (#12147) (#12148) on May 28, 2026, 10:14 PM
tobiu closed this issue on May 28, 2026, 10:14 PM