Skip to content

feat: add optional submodule initialization for worktrees#1189

Merged
Wirasm merged 6 commits intocoleam00:devfrom
halindrome:feat/worktree-submodule-init
Apr 15, 2026
Merged

feat: add optional submodule initialization for worktrees#1189
Wirasm merged 6 commits intocoleam00:devfrom
halindrome:feat/worktree-submodule-init

Conversation

@halindrome
Copy link
Copy Markdown
Contributor

@halindrome halindrome commented Apr 13, 2026

Summary

Fixes #1187

Adds a worktree.initSubmodules config option that runs git submodule update --init --recursive after worktree creation. This ensures workflows operating on monorepos with submodules have access to submodule content in their isolated worktrees.

Changes

  • packages/isolation/src/types.ts: Add initSubmodules?: boolean to WorktreeCreateConfig
  • packages/core/src/config/config-types.ts: Add initSubmodules?: boolean to RepoConfig.worktree
  • packages/isolation/src/providers/worktree.ts: Add initSubmodules() method called after git worktree add when configured. Checks for .gitmodules before running (no overhead for repos without submodules). Non-fatal — logs a warning if submodule init fails.
  • packages/isolation/src/providers/worktree.test.ts: 5 new tests covering: init when enabled, skip when disabled, skip when not configured, skip when no .gitmodules, and graceful failure.

Configuration

# .archon/config.yaml
worktree:
  initSubmodules: true  # default: false

Design decisions

  • Opt-in: Submodule init adds network I/O and time. Repos without submodules pay zero cost.
  • Non-fatal: Submodule init failure (network issues, auth problems) logs a warning but doesn't block worktree creation. The worktree is still usable for work that doesn't touch submodules.
  • 120s timeout: Submodule init can be slow for large repos with many submodules. The 120s timeout is 4x the standard git operation timeout.
  • Recursive: Uses --recursive to handle nested submodules.

Test plan

  • bun run type-check — all packages pass
  • bun run lint — zero warnings
  • bun run test — all tests pass (5 new tests for submodule init)
  • Manual: create a repo with submodules, set worktree.initSubmodules: true, run a workflow, verify submodule dirs are populated

Related

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • New worktree.initSubmodules option (defaults to true); worktree creation initializes git submodules when present unless explicitly disabled.
  • Bug Fixes / Behavior

    • Submodule init failures now surface and can block worktree creation. Absence of .gitmodules skips init; permission errors during .gitmodules access fail fast.
  • Tests

    • Added coverage for init on/off, missing vs permission access, failure cases, and invocation counting.
  • Documentation / Errors

    • Updated docs and user-facing error guidance for submodule initialization.

)

Repos with git submodules create worktrees with empty submodule
directories because git worktree add does not initialize them by
default. Add a configurable `worktree.initSubmodules` option in
.archon/config.yaml that runs `git submodule update --init --recursive`
after worktree creation when enabled.

The command only runs when .gitmodules exists in the worktree (no
overhead for repos without submodules) and is non-fatal — if submodule
init fails (e.g., network issues), the worktree is still created with
a warning logged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 13, 2026

Caution

Review failed

Pull request was closed or merged during review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds optional submodule initialization to worktree creation: provider probes for .gitmodules and, unless initSubmodules is false, runs git -C <worktreePath> submodule update --init --recursive; missing .gitmodules is skipped, probe errors warn and skip, and a failing git submodule command causes worktree creation to fail.

Changes

Cohort / File(s) Summary
Config & Types
packages/core/src/config/config-types.ts, packages/isolation/src/types.ts
Added optional initSubmodules?: boolean to RepoConfig.worktree and WorktreeCreateConfig to allow opting out of automatic submodule initialization (defaults to enabled when unset).
Worktree Provider
packages/isolation/src/providers/worktree.ts
Added initSubmodules(worktreePath) invoked after creating the worktree: probes <worktreePath>/.gitmodules via fs.promises.access (silently returns on ENOENT, throws/logs on other probe errors). If present and not disabled, runs git -C <worktreePath> submodule update --init --recursive with a 120s timeout; failures log and cause create() to reject. Also added resolve import from path.
Errors Classification
packages/isolation/src/errors.ts, packages/isolation/src/errors.test.ts
Centralized error patterns into ERROR_PATTERNS, added "Submodule initialization failed" pattern with a user-facing message that mentions initSubmodules: false, and updated isKnownIsolationError logic/tests accordingly.
Worktree Tests
packages/isolation/src/providers/worktree.test.ts
Adjusted fs.promises.access mock to treat .gitmodules as missing by default; added helpers to simulate .gitmodules present and to count git submodule update invocations. Added tests for default/true/false initSubmodules, missing .gitmodules, submodule init failure, probe EACCES, and submodule command timeout.
Docs
packages/docs-web/src/content/docs/reference/configuration.md
Documented worktree.initSubmodules option (default true) and described runtime behavior and failure semantics.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Provider as WorktreeProvider
    participant FS as FileSystem
    participant Git as Git CLI

    Client->>Provider: create(worktreePath, config{initSubmodules})
    Provider->>Provider: create branch / add worktree
    Provider->>FS: access(worktreePath/.gitmodules)
    alt .gitmodules missing (ENOENT)
        FS-->>Provider: ENOENT
        Provider->>Client: return active (no submodule init)
    else probe error (EACCES/other)
        FS-->>Provider: error
        Provider->>Provider: log warning / throw on probe error
        Provider->>Client: return/reject (probe error behavior)
    else .gitmodules exists and initSubmodules !== false
        FS-->>Provider: exists
        Provider->>Git: git -C worktreePath submodule update --init --recursive (120s)
        alt git succeeds
            Git-->>Provider: success
            Provider->>Client: return active
        else git fails
            Git-->>Provider: stderr/error
            Provider->>Client: throw "Submodule initialization failed"
        end
    else initSubmodules === false
        FS-->>Provider: (maybe exists)
        Provider->>Client: return active (skipped by config)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

I twitch my whiskers at .gitmodules bright,
I hop to fetch subtrees hidden from sight.
Say "no" and I pause, leave their doors shut tight;
say "yes" and I'll clone their little worlds right. 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add optional submodule initialization for worktrees' accurately and concisely describes the main change: adding an optional configuration option for submodule initialization in worktrees.
Description check ✅ Passed The PR description provides a clear summary with the problem and solution, lists all changed files with specific details, explains design decisions, and includes a test plan with passing validation commands.
Linked Issues check ✅ Passed The PR fully addresses issue #1187 by implementing configurable submodule initialization after worktree creation, including .gitmodules detection, graceful error handling, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes directly support the submodule initialization feature: configuration types, provider implementation, test coverage, error classification, and documentation updates are all within scope.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@halindrome halindrome marked this pull request as draft April 13, 2026 13:08
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/isolation/src/providers/worktree.test.ts (1)

820-918: Strong coverage for the new config-driven submodule flow.

These cases are well targeted. Consider adding one more case where .gitmodules access fails with non-ENOENT (e.g., EACCES) to lock in fully non-fatal behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/isolation/src/providers/worktree.test.ts` around lines 820 - 918,
Add a test to verify non-ENOENT errors from checking .gitmodules are treated
non-fatal: in the WorktreeProvider.create test suite, create a case similar to
the ENOENT test where mockAccess.mockImplementation throws a
NodeJS.ErrnoException with err.code = 'EACCES' when the path
endsWith('.gitmodules'); call new
WorktreeProvider(configLoader).create(baseRequest) and assert it does not throw,
that execSpy has no 'submodule' calls, and that the returned env.status (from
create) is 'active' to confirm the provider tolerates non-ENOENT access errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/isolation/src/providers/worktree.ts`:
- Around line 933-952: The .gitmodules existence probe in initSubmodules
currently can throw and escape the surrounding try/catch, making submodule init
fatal; modify initSubmodules to wrap the directoryExists(join(worktreePath,
'.gitmodules')) call in its own try/catch and treat any error the same as a
missing .gitmodules (i.e., log a debug/warn if you want and return early), so
only the execFileAsync('git', ['-C', worktreePath, 'submodule', 'update',
'--init', '--recursive'], ...) remains in the existing try/catch; reference the
initSubmodules method and the directoryExists call when applying the change.

---

Nitpick comments:
In `@packages/isolation/src/providers/worktree.test.ts`:
- Around line 820-918: Add a test to verify non-ENOENT errors from checking
.gitmodules are treated non-fatal: in the WorktreeProvider.create test suite,
create a case similar to the ENOENT test where mockAccess.mockImplementation
throws a NodeJS.ErrnoException with err.code = 'EACCES' when the path
endsWith('.gitmodules'); call new
WorktreeProvider(configLoader).create(baseRequest) and assert it does not throw,
that execSpy has no 'submodule' calls, and that the returned env.status (from
create) is 'active' to confirm the provider tolerates non-ENOENT access errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8c1d26e1-2a6b-4f70-9a1b-213ba777475e

📥 Commits

Reviewing files that changed from the base of the PR and between bf20063 and eb98868.

📒 Files selected for processing (4)
  • packages/core/src/config/config-types.ts
  • packages/isolation/src/providers/worktree.test.ts
  • packages/isolation/src/providers/worktree.ts
  • packages/isolation/src/types.ts

Comment thread packages/isolation/src/providers/worktree.ts
Move .gitmodules access check inside try/catch so permission errors
(EACCES) don't crash worktree creation. Use fs.access directly instead
of the misnamed directoryExists helper. Add test for EACCES path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@halindrome
Copy link
Copy Markdown
Contributor Author

QA Round 1 Report

Critical Issues

# Severity Confidence Finding Status
1 Critical 93 directoryExists call outside try/catch — non-ENOENT errors (e.g., EACCES) crash worktree creation Fixed in 85ce222

The .gitmodules access check was outside the non-fatal try/catch, meaning a permission error would propagate and abort worktree creation. Fixed by using fs.access directly inside its own try/catch — ENOENT means no submodules (skip silently), other errors log a warning and skip.

Important Issues

# Severity Confidence Finding Status
4 Important 82 copyConfiguredFiles has stale inline type (missing initSubmodules) Advisory — no functional impact
5 Important 85 directoryExists name misleading when used on a file Resolved by not using it — initSubmodules now calls access() directly
6 Important 80 120s timeout is unconfigurable and silent Advisory — documented in PR description
7 Important 85 Missing test for non-ENOENT error from .gitmodules check Fixed — added EACCES test in 85ce222

Advisory Notes

  • copyConfiguredFiles uses an inline type { baseBranch?: string; copyFiles?: string[] } that doesn't include initSubmodules. This is structurally compatible (TypeScript won't error) and copyConfiguredFiles doesn't need the field, but it's maintenance debt. Not blocking.
  • 120s timeout is 4x the standard 30s used elsewhere. Justified for large submodule trees but not configurable. Could add worktree.submoduleInitTimeout in a follow-up.

Verdict

Critical issue fixed. QA round 1 complete.

🤖 Generated with Claude Code

Rename worktree.submodules_initialized to worktree.submodule_init_completed
to pair with worktree.submodule_init_failed per CLAUDE.md convention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@halindrome
Copy link
Copy Markdown
Contributor Author

QA Round 2 Report

Findings

# Severity Confidence Finding Status
1 Important 82 Log event name mismatch: worktree.submodules_initialized doesn't pair with worktree.submodule_init_failed per CLAUDE.md convention Fixed in 9ebbc8f — renamed to worktree.submodule_init_completed

Verified Clean

  • Non-fatal contract: Both access() and execFileAsync wrapped in independent try/catch blocks. Confirmed no throw path from initSubmodules.
  • EACCES test: Properly verifies non-fatal behavior — asserts active env and no submodule exec calls.
  • Symlink edge case: access() follows symlinks. Broken symlink → ENOENT → skip (correct). Valid symlink → access succeeds → git reads resolved path (correct).
  • Config wire-up: initSubmodules flows from .archon/config.yamlRepoConfig.worktreeWorktreeCreateConfigcreateWorktree() without any missing mapping.
  • Scope: Minimal and focused — one config field, one method, one call site, six tests.

Verdict

QA round 2: CLEAN — ready for review.

🤖 Generated with Claude Code

Two refinements on top of the original submodule support:

1. Default changed from opt-in to auto-detect. Repos with .gitmodules
   present now have submodules initialized by default, matching git's
   own convention (submodule.recurse / clone --recurse-submodules). The
   .gitmodules check is an access() probe — repos without submodules pay
   zero cost. Explicit opt-out remains via worktree.initSubmodules: false.

2. Submodule init failure now throws instead of logging a warning. A
   worktree with uninitialized submodules is a silent broken state — the
   same class of bug this feature exists to prevent. The thrown error
   flows through classifyIsolationError, which now has a dedicated
   pattern suggesting opt-out if submodules are not needed by workflows.

EACCES on the .gitmodules read itself stays non-fatal — the git op would
fail the same way and double-erroring adds no value.

Tests updated: default-on behavior, explicit opt-in, explicit opt-out,
throw-on-git-failure (replacing the warn-and-continue test), EACCES
non-fatal path, and classifier coverage for the submodule pattern.
@Wirasm
Copy link
Copy Markdown
Collaborator

Wirasm commented Apr 15, 2026

Hey @halindrome — thanks for this, it closes a real gap and fits naturally with the recent worktree-primitive hardening series (#1198, #1206, #1212). I pushed two small follow-ups directly to your branch (maintainerCanModify: true — feel free to revert or amend if you disagree with the direction).

1. Default flipped from opt-in to auto-detect

Repos with .gitmodules present now have submodules initialized by default. Rationale: the .gitmodules probe is already a cheap access() check that short-circuits for non-submodule repos, so there's no cost to making it on-by-default. Defaulting to off means users with submodule monorepos (like yours) hit empty-dir state on first run — exactly the footgun this feature exists to prevent. Git itself takes the same stance with submodule.recurse / clone --recurse-submodules.

Explicit opt-out stays available via worktree.initSubmodules: false for users who don't want the fetch cost.

2. Submodule init failure now throws instead of warning

This one is the Archon convention more than a universal truth: CLAUDE.md calls out "Prefer throwing early with a clear error for unsupported or unsafe states — never silently swallow errors". The prior behavior (log.warn + return) produces a worktree that looks healthy but has empty submodule dirs — which is the same silent-wrong-state class of bug the recent worktree PRs have been closing (#1186, #1198, #1206). A user who opted in to submodule init needs to know when it didn't actually happen.

The thrown error flows through classifyIsolationError, which I extended with a dedicated pattern that surfaces the opt-out path in the error message:

Error: Submodule initialization failed. Check credentials and network access to submodule remotes, or set worktree.initSubmodules: false in .archon/config.yaml to opt out if submodules are not needed for your workflows.

EACCES on the .gitmodules read itself stays non-fatal (reading our own file is distinct from the git op; double-erroring adds no value).

Validation

  • bun run type-check — clean across all packages
  • bun --filter @archon/isolation test — 228 tests pass (worktree provider + errors + others)
  • bun run lint — zero warnings
  • bun run format:check — clean

Context on the broader class

Your four-issue root-cause mapping (#1183, #1187, #1188, #1192) has been incredibly useful — it's directly shaped the four PRs we've merged. The submodule init gap is orthogonal to the identity/coordination class those addressed; your issue framing made that clean separation possible. #1192 (codebase identity derivation) remains the underlying primitive rearchitect that would retire most of the guard code; that's the next logical step and will be tracked separately.

…e-init

# Conflicts:
#	packages/isolation/src/errors.ts
#	packages/isolation/src/providers/worktree.test.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/isolation/src/providers/worktree.test.ts (1)

848-851: Prefer reusing the submodule-call helper to avoid assertion drift.

The first new test re-implements call filtering inline instead of reusing countSubmoduleExecCalls(). Consolidating this keeps test intent consistent.

🧹 Small cleanup
-      const calls = execSpy.mock.calls.filter((call: unknown[]) => {
-        const args = call[1] as string[];
-        return args.includes('submodule');
-      });
-      expect(calls).toHaveLength(1);
-      expect(calls[0][1]).toEqual(
+      expect(countSubmoduleExecCalls()).toBe(1);
+      const submoduleCall = execSpy.mock.calls.find((call: unknown[]) => {
+        const args = call[1] as string[];
+        return args.includes('submodule') && args.includes('update');
+      });
+      expect(submoduleCall?.[1]).toEqual(
         expect.arrayContaining([
           '-C',
           expect.any(String),
           'submodule',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/isolation/src/providers/worktree.test.ts` around lines 848 - 851,
The test duplicates logic for filtering execSpy calls for 'submodule' instead of
reusing the existing helper countSubmoduleExecCalls(); update the test to call
countSubmoduleExecCalls(execSpy) (or the helper's expected signature) rather
than the inline filter so assertions use the centralized logic; locate the spot
using execSpy and replace the inline filter block with the helper call to ensure
consistent behavior across tests.
packages/isolation/src/errors.ts (1)

26-27: Reduce drift risk by deriving known patterns from errorPatterns.

The same literal patterns are now maintained in two places. Consider deriving knownPatterns from errorPatterns so classification and known-error detection stay in sync.

♻️ Suggested refactor
-  const knownPatterns = [
-    'permission denied',
-    'eacces',
-    'timeout',
-    'no space left',
-    'enospc',
-    'not a git repository',
-    'branch not found',
-    'no base branch configured',
-    'submodule initialization failed',
-  ];
+  const knownPatterns = errorPatterns.map(({ pattern }) => pattern);

Also applies to: 100-110

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/isolation/src/errors.ts` around lines 26 - 27, The code duplicates
pattern literals between errorPatterns and knownPatterns which causes drift;
update the implementation so knownPatterns is derived from errorPatterns (e.g.,
map errorPatterns to their pattern strings) and replace any hard-coded duplicate
arrays (including the similar block around the range referenced) with this
single source of truth; ensure functions that consume knownPatterns (and any
references to errorPatterns) use the derived array so classification and
known-error detection remain in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/isolation/src/errors.ts`:
- Around line 26-27: The code duplicates pattern literals between errorPatterns
and knownPatterns which causes drift; update the implementation so knownPatterns
is derived from errorPatterns (e.g., map errorPatterns to their pattern strings)
and replace any hard-coded duplicate arrays (including the similar block around
the range referenced) with this single source of truth; ensure functions that
consume knownPatterns (and any references to errorPatterns) use the derived
array so classification and known-error detection remain in sync.

In `@packages/isolation/src/providers/worktree.test.ts`:
- Around line 848-851: The test duplicates logic for filtering execSpy calls for
'submodule' instead of reusing the existing helper countSubmoduleExecCalls();
update the test to call countSubmoduleExecCalls(execSpy) (or the helper's
expected signature) rather than the inline filter so assertions use the
centralized logic; locate the spot using execSpy and replace the inline filter
block with the helper call to ensure consistent behavior across tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f9bdbe38-2abf-460b-ba64-8e601eaf6c7d

📥 Commits

Reviewing files that changed from the base of the PR and between 9ebbc8f and c64f1ae.

📒 Files selected for processing (6)
  • packages/core/src/config/config-types.ts
  • packages/isolation/src/errors.test.ts
  • packages/isolation/src/errors.ts
  • packages/isolation/src/providers/worktree.test.ts
  • packages/isolation/src/providers/worktree.ts
  • packages/isolation/src/types.ts
✅ Files skipped from review due to trivial changes (3)
  • packages/isolation/src/types.ts
  • packages/core/src/config/config-types.ts
  • packages/isolation/src/providers/worktree.ts

@Wirasm
Copy link
Copy Markdown
Collaborator

Wirasm commented Apr 15, 2026

PR Review Summary (multi-agent)

Ran code-reviewer, docs-impact, pr-test-analyzer, silent-failure-hunter, and type-design-analyzer against the diff.

Critical Issues

None.

Important Issues

Agent Issue Location
code-reviewer + silent-failure-hunter Non-ENOENT errors on the .gitmodules access() check (e.g. EACCES) are logged at warn and silently skip submodule init. With initSubmodules defaulting to true, a .gitmodules-present-but-unreadable worktree is returned as if submodules initialized — exactly the silent-broken-state this PR exists to prevent. The test explicitly encodes this behavior as correct. Recommend: treat non-ENOENT access errors as fatal (re-throw via the same classified error) or at minimum surface to the user, not just logs. packages/isolation/src/providers/worktree.ts ~lines 1037-1049; test at worktree.test.ts ~line 1058
type-design-analyzer initSubmodules?: boolean with @default true violates the standard optional-property reading (undefined conventionally means false). Here undefinedtrue. Recommend either making it non-optional with an explicit default at the parse/merge boundary, or very prominent docs. Also: WorktreeCreateConfig.initSubmodules and RepoConfig.worktree.initSubmodules are structurally duplicated — drift risk. packages/isolation/src/types.ts:242-253, packages/core/src/config/config-types.ts:143-160
docs-impact New worktree.initSubmodules option is absent from the user-facing config reference. Since it's now default-on, users on repos with unreachable submodule remotes will silently break until they discover the opt-out. packages/docs-web/src/content/docs/reference/configuration.md ~line 119 (worktree block)

Suggestions

Agent Suggestion Location
pr-test-analyzer Timeout path (120s execFileAsync timeout) is untested. A short test asserting the rejection message still matches /Submodule initialization failed/ and that classifyIsolationError returns opt-out guidance would close the loop. Criticality 6/10. worktree.test.ts
pr-test-analyzer "Initializes when explicitly opted in" test only counts calls, doesn't assert --recursive in args. Low effort. worktree.test.ts ~lines 1000-1012
type-design-analyzer No Zod validation on the parsed config — contract is only enforced by TS. Convention for engine schemas is Zod-derived. config-types.ts

Strengths

  • execFileAsync used correctly (not exec).
  • Inner git-command catch logs at error with full context and re-throws a classifiable error — no swallowing on the hot path.
  • classifyIsolationError registers the new "submodule initialization failed" pattern with actionable opt-out guidance, and isKnownIsolationError is updated in sync.
  • Fail-fast on git-command failure is correct and tested.
  • Default-on behavior tested both via absent config and undefined field.
  • EACCES branch is at least explicitly tested (just encoded as "acceptable silent-skip", which is the disagreement above).

Verdict

NEEDS FIXES — the EACCES silent-skip is the only material issue; the rest are polish. No critical blockers.

Recommended Actions

  1. Decide on EACCES behavior: either re-throw as classified error (preferred — matches fail-fast guideline) or surface to user via platform message. Update the test accordingly.
  2. Add initSubmodules to packages/docs-web/src/content/docs/reference/configuration.md worktree block.
  3. Consider resolving the ?: boolean + @default true mismatch (make non-optional with default, or document prominently).
  4. Optional: add timeout-path test; assert --recursive in opt-in test.

…cs, dedupe

Review feedback from multi-agent pass on PR coleam00#1189:

1. EACCES on .gitmodules read now throws instead of silently skipping.
   The prior non-fatal path produced a worktree that looked successful
   but had empty submodule dirs — the exact silent-broken-state this
   feature exists to prevent. "The git op would fail the same way"
   was wrong reasoning: surfacing one classified error beats surfacing
   zero. Only ENOENT (no submodules) remains non-fatal.

2. Add `worktree.initSubmodules` to docs/reference/configuration.md
   with behavior section explaining default-on semantic and opt-out.

3. Collapse errorPatterns and knownPatterns into a single ERROR_PATTERNS
   source with `known: boolean` per entry. Removes the drift risk
   flagged by the reviewer and makes the intentional divergence
   ("cannot extract owner/repo" is classifiable but NOT known — it
   crashes as a user-input bug) explicit.

4. Tests: assert --recursive in opt-in test (was only in default-on),
   flip EACCES test to assert throw, add timeout-path test.
@Wirasm Wirasm merged commit f61d576 into coleam00:dev Apr 15, 2026
1 of 4 checks passed
joaobmonteiro pushed a commit to joaobmonteiro/Archon that referenced this pull request Apr 26, 2026
Worktrees created via `git worktree add` do not initialize submodules — monorepo workflows that need submodule content find empty directories. Auto-detect `.gitmodules` and run `git submodule update --init --recursive` after worktree creation; classify failures through the isolation error pipeline.

Behavior:
- `.gitmodules` absent → skip silently (zero-cost probe, no effect on non-submodule repos)
- `.gitmodules` present → run submodule init by default (opt out via `worktree.initSubmodules: false`)
- submodule init or `.gitmodules` read failure → throw with classified error including opt-out guidance
- Only `ENOENT` on `.gitmodules` is treated as "no submodules"; other access errors (EACCES, EIO) surface as failures to prevent silent empty-dir worktrees

Changes:
- `packages/isolation/src/providers/worktree.ts` — `initSubmodules()` method + call site in `createWorktree()`
- `packages/isolation/src/errors.ts` — collapsed `errorPatterns` + `knownPatterns` into single `ERROR_PATTERNS` source of truth with `known: boolean` per entry; added submodule pattern with opt-out guidance
- `packages/isolation/src/types.ts` + `packages/core/src/config/config-types.ts` — new `initSubmodules?: boolean` config option
- `packages/docs-web/src/content/docs/reference/configuration.md` — documented the new option and submodule behavior
- Tests: default-on, explicit opt-in, explicit opt-out, skip-when-absent, fail-fast on EACCES, fail-fast on git failure, fail-fast on timeout

Credit to @halindrome for the original implementation and root-cause mapping across coleam00#1183, coleam00#1187, coleam00#1188, coleam00#1192.

Follow-up: coleam00#1192 (codebase identity rearchitect) would retire the cross-clone guard code in `resolver.ts` and `worktree.ts` that coleam00#1198, coleam00#1206 added. Separate PR.

Closes coleam00#1187
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Worktrees created from monorepos do not initialize git submodules

3 participants