auto-claude: 218-enable-claude-code-features-in-worktree-terminals (#1809)

* auto-claude: subtask-1-1 - Create symlinkClaudeConfigToWorktree() function

Add function to symlink project root's .claude/ directory into terminal
worktrees, enabling Claude Code features in isolated workspaces. Follows
the exact pattern from symlinkNodeModulesToWorktree().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* auto-claude: subtask-1-2 - Call symlinkClaudeConfigToWorktree() in createTerminalWorktree

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* auto-claude: subtask-2-1 - Create symlink_claude_config_to_worktree() function

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* auto-claude: subtask-2-2 - Call symlink_claude_config_to_worktree() in setup_workspace

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* auto-claude: subtask-3-2 - Run frontend TypeScript compilation check and existing tests

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Andy
2026-02-13 14:59:53 +01:00
committed by GitHub
parent 6204d5fc2b
commit e3b219288e
2 changed files with 138 additions and 0 deletions
+76
View File
@@ -283,6 +283,75 @@ def symlink_node_modules_to_worktree(
return symlinked
def symlink_claude_config_to_worktree(
project_dir: Path, worktree_path: Path
) -> list[str]:
"""
Symlink .claude/ directory from project root to worktree.
This ensures the worktree has access to Claude Code configuration
(settings, CLAUDE.md, MCP servers, etc.) so that terminals opened
in the worktree behave identically to the project root.
Args:
project_dir: The main project directory
worktree_path: Path to the worktree
Returns:
List of symlinked paths (relative to worktree)
"""
symlinked = []
source_path = project_dir / ".claude"
target_path = worktree_path / ".claude"
# Skip if source doesn't exist
if not source_path.exists():
debug(MODULE, "Skipping .claude/ - source does not exist")
return symlinked
# Skip if target already exists
if target_path.exists():
debug(MODULE, "Skipping .claude/ - target already exists")
return symlinked
# Also skip if target is a symlink (even if broken)
if target_path.is_symlink():
debug(MODULE, "Skipping .claude/ - symlink already exists (possibly broken)")
return symlinked
# Ensure parent directory exists
target_path.parent.mkdir(parents=True, exist_ok=True)
try:
if sys.platform == "win32":
# On Windows, use junctions instead of symlinks (no admin rights required)
result = subprocess.run(
["cmd", "/c", "mklink", "/J", str(target_path), str(source_path)],
capture_output=True,
text=True,
)
if result.returncode != 0:
raise OSError(result.stderr or "mklink /J failed")
else:
# On macOS/Linux, use relative symlinks for portability
relative_source = os.path.relpath(source_path, target_path.parent)
os.symlink(relative_source, target_path)
symlinked.append(".claude")
debug(MODULE, f"Symlinked .claude/ -> {source_path}")
except OSError as e:
debug_warning(
MODULE,
f"Could not symlink .claude/: {e}. Claude Code features may not work in worktree terminals.",
)
print_status(
"Warning: Could not link .claude/ - Claude Code features may not work in terminals",
"warning",
)
return symlinked
def copy_spec_to_worktree(
source_spec_dir: Path,
worktree_path: Path,
@@ -382,6 +451,13 @@ def setup_workspace(
if symlinked_modules:
print_status(f"Dependencies linked: {', '.join(symlinked_modules)}", "success")
# Symlink .claude/ config to worktree for Claude Code features (settings, commands, etc.)
symlinked_claude = symlink_claude_config_to_worktree(
project_dir, worktree_info.path
)
if symlinked_claude:
print_status(f"Claude config linked: {', '.join(symlinked_claude)}", "success")
# Copy security configuration files if they exist
# Note: Unlike env files, security files always overwrite to ensure
# the worktree uses the same security rules as the main project.
@@ -312,6 +312,62 @@ function symlinkNodeModulesToWorktree(projectPath: string, worktreePath: string)
return symlinked;
}
/**
* Symlink the project root's .claude/ directory into a terminal worktree.
* This enables Claude Code features (settings, commands, memory) in worktree terminals.
* Follows the same pattern as symlinkNodeModulesToWorktree().
*/
function symlinkClaudeConfigToWorktree(projectPath: string, worktreePath: string): string[] {
const symlinked: string[] = [];
const sourceRel = '.claude';
const sourcePath = path.join(projectPath, sourceRel);
const targetPath = path.join(worktreePath, sourceRel);
// Skip if source doesn't exist
if (!existsSync(sourcePath)) {
debugLog('[TerminalWorktree] Skipping .claude symlink - source does not exist:', sourcePath);
return symlinked;
}
// Skip if target already exists
if (existsSync(targetPath)) {
debugLog('[TerminalWorktree] Skipping .claude symlink - target already exists:', targetPath);
return symlinked;
}
// Also skip if target is a symlink (even if broken)
try {
lstatSync(targetPath);
debugLog('[TerminalWorktree] Skipping .claude symlink - target exists (possibly broken symlink):', targetPath);
return symlinked;
} catch {
// Target doesn't exist at all - good, we can create symlink
}
// Ensure parent directory exists
const targetDir = path.dirname(targetPath);
if (!existsSync(targetDir)) {
mkdirSync(targetDir, { recursive: true });
}
try {
if (isWindows()) {
symlinkSync(sourcePath, targetPath, 'junction');
debugLog('[TerminalWorktree] Created .claude junction (Windows):', sourceRel, '->', sourcePath);
} else {
const relativePath = path.relative(path.dirname(targetPath), sourcePath);
symlinkSync(relativePath, targetPath);
debugLog('[TerminalWorktree] Created .claude symlink (Unix):', sourceRel, '->', relativePath);
}
symlinked.push(sourceRel);
} catch (error) {
debugError('[TerminalWorktree] Could not create symlink for .claude:', error);
}
return symlinked;
}
function saveWorktreeConfig(projectPath: string, name: string, config: TerminalWorktreeConfig): void {
const metadataDir = getTerminalWorktreeMetadataDir(projectPath);
mkdirSync(metadataDir, { recursive: true });
@@ -523,6 +579,12 @@ async function createTerminalWorktree(
debugLog('[TerminalWorktree] Symlinked dependencies:', symlinkedModules.join(', '));
}
// Symlink .claude/ config for Claude Code features (settings, commands, memory)
const symlinkedClaude = symlinkClaudeConfigToWorktree(projectPath, worktreePath);
if (symlinkedClaude.length > 0) {
debugLog('[TerminalWorktree] Symlinked Claude config:', symlinkedClaude.join(', '));
}
const config: TerminalWorktreeConfig = {
name,
worktreePath,