How to Troubleshoot Git Worktree Isolation in the Epic 1 Worktree Manager of aios-core

To troubleshoot Git worktree isolation issues in SynkraAI/aios-core, verify your core-config.yaml settings, run diagnostic commands via story-worktree-hooks.js, and use the WorktreeManager API to detect conflicts and clean up stale worktrees.

The Epic 1 Worktree Manager in the SynkraAI/aios-core repository provides automated Git worktree isolation for story-based development, ensuring each feature branch operates in a dedicated directory under .aios/worktrees. When developers encounter "worktree already exists" errors, stale isolation directories, or merge conflicts, systematic troubleshooting using the built-in CLI hooks and JavaScript API restores repository integrity. This guide covers diagnostic steps and resolution strategies for common Git worktree isolation failures.

Architecture of the Epic 1 Worktree Manager

The isolation system relies on four primary components that orchestrate Git worktree operations and enforce repository boundaries.

Core Components

Isolation Guarantees

Each worktree receives a dedicated branch (auto-claude/<storyId>) and directory under .aios/worktrees/<storyId>. The manager records creation timestamps and uncommitted change counts, marking worktrees as stale after exceeding the staleDays threshold.

Common Git Worktree Isolation Failures and Solutions

"Worktree Already Exists" Errors

Symptom: The error Worktree already exists appears when starting a story.

Root Cause: A previous worktree was left behind from a failed run or manual interruption.

Diagnostic Steps:

  1. Run node .aios-core/infrastructure/scripts/story-worktree-hooks.js status <story-file>.
  2. Verify hasWorktree: true and note its path.

Resolution:

  • If the worktree is still needed, creation is skipped (the --force flag is ignored for existing worktrees).
  • If stale, run node .aios-core/infrastructure/scripts/story-worktree-hooks.js done <story-file> and select cleanup, or use --keep-worktree to preserve data while removing the Git worktree reference.

Maximum Worktree Limit Reached

Symptom: Error Maximum worktrees limit (10) reached prevents new story isolation.

Root Cause: The repository contains the maximum number of active worktrees (manager.getCount().total >= maxWorktrees).

Diagnostic Steps:

  1. Run node .aios-core/infrastructure/scripts/story-worktree-hooks.js list or use the manager's list() method in a Node REPL.
  2. Identify stale or completed worktrees by checking timestamps.

Resolution:

Delete unwanted worktrees manually:

node .aios-core/infrastructure/scripts/story-worktree-hooks.js done <story-file> --keep-worktree

Then force remove via API:

const WM = require('./.aios-core/infrastructure/scripts/worktree-manager');
const mgr = new WM(process.cwd());
await mgr.remove('STORY-42', {force: true});

Alternatively, enable autoCleanup: true in .aios-core/development/workflows/auto-worktree.yaml to automatically purge stale worktrees.

Uncommitted Changes Blocking Cleanup

Symptom: Worktree has X uncommitted changes error during onStoryDone cleanup.

Root Cause: The worktree contains local edits that haven't been committed or stashed.

Diagnostic Steps:

  1. Run node .aios-core/infrastructure/scripts/story-worktree-hooks.js status <story-file> to see uncommittedChanges > 0.
  2. Run git status inside the worktree directory (.aios/worktrees/<storyId>).

Resolution:

Commit or stash changes before cleanup:

cd .aios/worktrees/STORY-42
git add .
git commit -m "WIP: save before worktree cleanup"

Alternatively, use the done hook with merge option first:

node .aios-core/infrastructure/scripts/story-worktree-hooks.js done <story-file>

Select merge to integrate changes into the base branch, then proceed with cleanup.

Git Command Failures and Repository State Issues

Symptom: Raw Git errors like git worktree add … failing.

Root Cause: Repository is not a clean Git tree (detached HEAD, unresolved merge, missing .git directory).

Diagnostic Steps:

  1. Run pre-flight checks manually:

    git rev-parse --is-inside-work-tree
    git worktree list
  2. Check for detached HEAD:

    git symbolic-ref --short HEAD

Resolution:

  • Resolve any Git conflicts and ensure you are on a valid branch.
  • If the repository is corrupted, reclone it and reinitialize the worktree manager.
  • Ensure the workflow's pre_flight block commands pass before invoking auto-worktree.yaml.

Stale Worktree Detection and Removal

Symptom: Dashboard shows outdated worktrees or status.json contains invalid paths.

Root Cause: Worktrees exceed the staleDays threshold (default 30 days) or were manually deleted outside the manager.

Diagnostic Steps:

  1. Inspect manager.getCount() to see stale counts.
  2. Check .aios/status.json for activeWorktree field accuracy.

Resolution:

Force clean stale worktrees:

const WM = require('./.aios-core/infrastructure/scripts/worktree-manager');
const mgr = new WM(process.cwd());
const removed = await mgr.cleanupStale();
console.log('Cleaned up stale worktrees:', removed);

Diagnostic Commands and Debugging Techniques

Using the story-worktree-hooks.js CLI

The high-level CLI provides quick diagnostics without writing code:


# Check specific story status

node .aios-core/infrastructure/scripts/story-worktree-hooks.js status docs/stories/story-1.4.md

# List all worktrees

node .aios-core/infrastructure/scripts/story-worktree-hooks.js list

# Check effective configuration

node .aios-core/infrastructure/scripts/story-worktree-hooks.js config

Programmatic Inspection with WorktreeManager

For deeper debugging, use the Node.js API directly:

const WorktreeManager = require('./.aios-core/infrastructure/scripts/worktree-manager');

(async () => {
  const mgr = new WorktreeManager(process.cwd());
  
  // Get comprehensive counts
  const counts = await mgr.getCount();
  console.log(`Total: ${counts.total}, Active: ${counts.active}, Stale: ${counts.stale}`);
  
  // List all with metadata
  const worktrees = await mgr.list();
  console.log(worktrees);
  
  // Check specific worktree path
  const path = mgr.getWorktreePath('STORY-42');
  console.log('Worktree location:', path);
  
  // Detect merge conflicts before attempting merge
  const conflicts = await mgr.detectConflicts('STORY-42');
  if (conflicts.length) {
    console.warn('Conflicts detected:', conflicts);
  }
})();

Enabling Verbose Logging

Set configuration flags to see detailed Git command execution:


# Environment variable

export AIOS_DEBUG=1

# Or set in auto-worktree.yaml

config:
  verbose: true

With verbose mode enabled, the manager prints chalk-colored logs for each git worktree command, making it easy to spot where failures occur.

Code Examples for Troubleshooting

Run the High-Level Hook from the CLI


# When a story moves to "In Progress"

node .aios-core/infrastructure/scripts/story-worktree-hooks.js start docs/stories/story-1.4.md

# When a story is marked "Done"

node .aios-core/infrastructure/scripts/story-worktree-hooks.js done docs/stories/story-1.4.md

Add --force to bypass a manual autoCreate setting, or --keep-worktree to prevent auto-cleanup.

Detect Conflicts Before Merging a Story

const WM = require('./.aios-core/infrastructure/scripts/worktree-manager');

(async () => {
  const mgr = new WM(process.cwd());
  const conflicts = await mgr.detectConflicts('STORY-42');
  if (conflicts.length) {
    console.warn('Conflicts would arise:', conflicts);
  } else {
    console.log('Safe to merge.');
  }
})();

Force-Clean Stale Worktrees

const WM = require('./.aios-core/infrastructure/scripts/worktree-manager');

(async () => {
  const mgr = new WM(process.cwd());
  const removed = await mgr.cleanupStale();   // returns array of storyIds removed
  console.log('Cleaned up stale worktrees:', removed);
})();

Manually Merge a Worktree with Audit Log

const WM = require('./.aios-core/infrastructure/scripts/worktree-manager');

(async () => {
  const mgr = new WM(process.cwd());
  const result = await mgr.mergeToBase('STORY-42', {
    squash: true,
    cleanup: true,
    message: 'Feature complete for STORY-42',
  });
  console.log('Merge result:', result);
})();

The mergeToBase method writes a JSON audit file under .aios/logs/merges/.

Summary

  • Git worktree isolation in aios-core relies on the WorktreeManager class in .aios-core/infrastructure/scripts/worktree-manager.js, which wraps native Git commands and enforces limits via maxWorktrees (default 10) and staleDays (default 30).
  • When troubleshooting, start with CLI diagnostics provided by story-worktree-hooks.js (status, list, config) to quickly identify existing worktrees and configuration issues.
  • For "Worktree already exists" errors, verify with status and use the done hook with cleanup options, or force-remove via the API with manager.remove(storyId, {force: true}).
  • Resolve maximum limit errors by cleaning stale worktrees using manager.cleanupStale() or enabling autoCleanup: true in auto-worktree.yaml.
  • Address uncommitted changes by committing or stashing inside the worktree directory before invoking cleanup, or merge first using mergeToBase().
  • Use verbose logging (AIOS_DEBUG=1 or config.verbose: true) and the Node.js REPL to inspect getCount(), list(), and detectConflicts() for deep debugging.

Frequently Asked Questions

How do I check if a story already has an active worktree?

Run the status command using the story file path:

node .aios-core/infrastructure/scripts/story-worktree-hooks.js status docs/stories/story-1.4.md

This returns JSON indicating hasWorktree: true or false, along with the filesystem path and uncommitted change count. You can also programmatically verify existence using manager.getWorktreePath(storyId) to check if the directory is registered in Git's worktree list.

What is the default maximum number of worktrees allowed?

The default maximum worktree limit is 10, controlled by the maxWorktrees parameter in core-config.yaml or auto-worktree.yaml. When manager.getCount().total reaches this threshold, new worktree creation fails with a "Maximum worktrees limit reached" error. You can increase this limit in your configuration file or reduce the current count by removing stale worktrees via manager.cleanupStale().

How do I force remove a worktree that is marked as stale?

Use the WorktreeManager API with the force option to bypass safety checks:

const WM = require('./.aios-core/infrastructure/scripts/worktree-manager');
const mgr = new WM(process.cwd());
await mgr.remove('STORY-42', {force: true});

Alternatively, run cleanupStale() to automatically remove all worktrees exceeding the staleDays threshold (default 30 days). For CLI-based removal, use node story-worktree-hooks.js done <story-file> followed by selecting the cleanup option, which handles both the Git worktree removal and directory cleanup.

Why does the cleanup fail with uncommitted changes?

The onStoryDone hook and cleanupStale() methods check for uncommitted changes before removing a worktree to prevent accidental data loss. If git status detects modified or untracked files, the manager throws a "Worktree has X uncommitted changes" error and aborts the cleanup. To resolve this, navigate to the worktree directory (.aios/worktrees/<storyId>), commit or stash your changes, then retry the cleanup. Alternatively, use mergeToBase() first to integrate changes into the main branch before removing the worktree.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →