Enable-ClaudeMemoryRedirect.ps1
|
function Enable-ClaudeMemoryRedirect { <# .SYNOPSIS Redirects ~/.claude/projects to $env:CLAUDE_MEMORY_DIR via a junction (Windows) or symlink (Linux/macOS). .DESCRIPTION Claude Code writes per-project memory and session state under ~/.claude/projects. This function lets the user point that directory at an alternate location (synced storage, work-only drive, etc.) by setting $env:CLAUDE_MEMORY_DIR. When set, existing content in ~/.claude/projects is migrated to the target with Copy-Item -Verbose -ErrorAction Stop, the original is removed, and a directory junction/symlink is created in its place. If $env:CLAUDE_MEMORY_DIR is not set, a one-time tip is printed offering to configure it, with a Y/n/never/exit prompt mirroring the CLAUDE_USER_MD startup hint. .PARAMETER HomeDir Platform-appropriate user home directory (typically [Environment]::GetFolderPath('UserProfile')). .PARAMETER Force Passes -Force to Copy-Item during migration so conflicting files at the target are overwritten. Without this switch, Copy-Item fails on any destination collision and the migration aborts. #> [CmdletBinding()] param( [Parameter(Mandatory)][string]$HomeDir, [switch]$Force ) $projectsDir = Join-Path $HomeDir '.claude' 'projects' $suppressFile = Join-Path $HomeDir '.claude' 'suppress_memory_dir_hint' if (-not $env:CLAUDE_MEMORY_DIR) { if (Test-Path $suppressFile) { Write-Verbose "CLAUDE_MEMORY_DIR not set; tip suppressed." return } Write-Host "" Write-Host "Tip: set `$env:CLAUDE_MEMORY_DIR to a directory where Claude should store memories." Write-Host " PwrClaude will redirect ~/.claude/projects there via a junction so memories persist" Write-Host " across machines (OneDrive, work share, etc.). Set this in a LOCAL profile snippet," Write-Host " not in a shared/synced profile — personal and work machines should point to different" Write-Host " stores so work memories never land in personal sync and vice versa." Write-Host "" Write-Host " The value is evaluated as a PowerShell expression, so you can use variables:" Write-Host " `$env:CLAUDE_MEMORY_DIR = '`${env:OneDrive}\Claude\projects'" Write-Host " `$env:CLAUDE_MEMORY_DIR = '`${env:USERPROFILE}\claude-memory'" Write-Host "" $answer = Read-Host "Show this tip next time? [Y/n/never/e(xit)]" if ($answer -match '^[nN]ever$') { New-Item -ItemType File -Path $suppressFile -Force | Out-Null Write-Host "Got it — tip suppressed permanently. Set CLAUDE_MEMORY_DIR any time to enable the redirect." } elseif ($answer -match '^[nN]$') { New-Item -ItemType File -Path $suppressFile -Force | Out-Null Write-Host "Got it — tip suppressed. Set CLAUDE_MEMORY_DIR any time to enable the redirect." } elseif ($answer -match '^([eE]|[eE]xit)$') { exit 0 } Write-Host "" return } # Resolve as a PowerShell expression (matches the CLAUDE_USER_MD pattern in Invoke-Claude.ps1). try { $targetDir = Invoke-Expression "`"$env:CLAUDE_MEMORY_DIR`"" } catch { Write-Warning "CLAUDE_MEMORY_DIR expression failed to evaluate: $env:CLAUDE_MEMORY_DIR" Write-Warning "Error: $_" return } if (-not $targetDir) { Write-Warning "CLAUDE_MEMORY_DIR resolved to empty; skipping redirect." return } Write-Verbose "CLAUDE_MEMORY_DIR resolved to: $targetDir" if (-not (Test-Path $targetDir)) { Write-Verbose "Creating target directory: $targetDir" New-Item -ItemType Directory -Path $targetDir -Force | Out-Null } $claudeDir = Join-Path $HomeDir '.claude' if (-not (Test-Path $claudeDir)) { New-Item -ItemType Directory -Path $claudeDir -Force | Out-Null } $link = Get-Item -LiteralPath $projectsDir -Force -ErrorAction SilentlyContinue if ($link -and $link.LinkType) { $currentTarget = @($link.Target)[0] $targetResolved = (Resolve-Path -LiteralPath $targetDir).Path $currentResolved = try { (Resolve-Path -LiteralPath $currentTarget).Path } catch { $currentTarget } if ($currentResolved -eq $targetResolved) { Write-Verbose "~/.claude/projects already redirected to $targetDir" return } Write-Warning "~/.claude/projects is already a $($link.LinkType) pointing to '$currentTarget', not to '$targetDir'. Skipping redirect." return } if ($link) { $contents = Get-ChildItem -LiteralPath $projectsDir -Force -ErrorAction SilentlyContinue if ($contents) { Write-Verbose "Migrating existing memories from $projectsDir to $targetDir" foreach ($item in $contents) { $dest = Join-Path $targetDir $item.Name if ((Test-Path $dest) -and -not $Force) { Write-Verbose "Skipping '$($item.Name)' — already exists in target (use -ForceMemoryRedirect to overwrite)" continue } $copyParams = @{ LiteralPath = $item.FullName Destination = $targetDir Recurse = $true Force = [bool]$Force ErrorAction = 'Stop' } try { Copy-Item @copyParams } catch { Write-Warning "Failed to migrate '$($item.Name)': $_" Write-Warning "Re-run with 'claude -ForceMemoryRedirect' to overwrite, or move the item manually." throw } } } Write-Verbose "Removing original directory at $projectsDir" Remove-Item -LiteralPath $projectsDir -Recurse -Force -ErrorAction Stop } $onWindows = $IsWindows -or $env:OS -eq 'Windows_NT' $linkType = if ($onWindows) { 'Junction' } else { 'SymbolicLink' } Write-Verbose "Creating $linkType at $projectsDir -> $targetDir" New-Item -ItemType $linkType -Path $projectsDir -Target $targetDir -ErrorAction Stop | Out-Null Write-Host "Redirected $projectsDir -> $targetDir ($linkType)" -ForegroundColor Green } |