Created
January 12, 2026 05:29
-
-
Save Haleclipse/1c35fe7b98fe1b5ea0523eb4d3a10f0b to your computer and use it in GitHub Desktop.
Fix Claude Code Windows path issue (v2.1.5) - Bash tool creates temp files in cwd instead of temp directory
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Claude Code Windows Path Fix Script | |
| .DESCRIPTION | |
| Fixes the Windows Bash tool temp file path issue in Claude Code. | |
| THE BUG: | |
| On Windows + Git Bash, Claude Code uses Node.js path.join() to generate | |
| temp file paths. However, path.join on Windows produces backslashes (\). | |
| When this path is passed to Bash, backslashes are interpreted as escape chars: | |
| - \tmp\claude-xxxx-cwd -> tmpclaude-xxxx-cwd (relative path!) | |
| This causes temp files to be incorrectly created in the current working directory. | |
| THE FIX: | |
| This script patches the code to convert backslashes to forward slashes | |
| on Windows before the path is used in Bash commands: | |
| if(LQ()==="windows")H=H.replace(/\\/g,"/"); | |
| .PARAMETER Check | |
| Check if fix is needed without making changes | |
| .PARAMETER Restore | |
| Restore original file from backup | |
| .PARAMETER Help | |
| Show help information | |
| .EXAMPLE | |
| .\apply-claude-code-windows-path-fix-en.ps1 | |
| Apply the fix | |
| .EXAMPLE | |
| .\apply-claude-code-windows-path-fix-en.ps1 -Check | |
| Check if fix is needed | |
| .EXAMPLE | |
| .\apply-claude-code-windows-path-fix-en.ps1 -Restore | |
| Restore from backup | |
| .NOTES | |
| Note: This patch will be overwritten when Claude Code updates. | |
| Re-run this script after updates if the issue reoccurs. | |
| #> | |
| param( | |
| [switch]$Check, | |
| [switch]$Restore, | |
| [switch]$Help | |
| ) | |
| # Color output functions | |
| function Write-Success { param($Message) Write-Host "[OK] " -ForegroundColor Green -NoNewline; Write-Host $Message } | |
| function Write-Warning { param($Message) Write-Host "[!] " -ForegroundColor Yellow -NoNewline; Write-Host $Message } | |
| function Write-Error { param($Message) Write-Host "[X] " -ForegroundColor Red -NoNewline; Write-Host $Message } | |
| function Write-Info { param($Message) Write-Host "[>] " -ForegroundColor Blue -NoNewline; Write-Host $Message } | |
| # Show help | |
| if ($Help) { | |
| Get-Help $MyInvocation.MyCommand.Path -Detailed | |
| exit 0 | |
| } | |
| # Find Claude Code cli.js path | |
| function Find-CliPath { | |
| $locations = @( | |
| (Join-Path $env:USERPROFILE ".claude\local\node_modules\@anthropic-ai\claude-code\cli.js"), | |
| (Join-Path $env:APPDATA "npm\node_modules\@anthropic-ai\claude-code\cli.js"), | |
| (Join-Path $env:ProgramFiles "nodejs\node_modules\@anthropic-ai\claude-code\cli.js"), | |
| (Join-Path ${env:ProgramFiles(x86)} "nodejs\node_modules\@anthropic-ai\claude-code\cli.js") | |
| ) | |
| # Try to get global path from npm | |
| try { | |
| $npmRoot = & npm root -g 2>$null | |
| if ($npmRoot) { | |
| $locations += Join-Path $npmRoot "@anthropic-ai\claude-code\cli.js" | |
| } | |
| } catch {} | |
| foreach ($path in $locations) { | |
| if (Test-Path $path) { | |
| return $path | |
| } | |
| } | |
| return $null | |
| } | |
| $cliPath = Find-CliPath | |
| if (-not $cliPath) { | |
| Write-Error "Claude Code cli.js not found" | |
| Write-Host "" | |
| Write-Host "Searched locations:" | |
| Write-Host " ~\.claude\local\node_modules\@anthropic-ai\claude-code\cli.js" | |
| Write-Host " %APPDATA%\npm\node_modules\@anthropic-ai\claude-code\cli.js" | |
| Write-Host " %ProgramFiles%\nodejs\node_modules\@anthropic-ai\claude-code\cli.js" | |
| Write-Host " `$(npm root -g)\@anthropic-ai\claude-code\cli.js" | |
| exit 1 | |
| } | |
| # Restore backup | |
| if ($Restore) { | |
| $backups = Get-ChildItem -Path (Split-Path $cliPath) -Filter "cli.js.backup-winpath-*" -ErrorAction SilentlyContinue | | |
| Sort-Object LastWriteTime -Descending | |
| if ($backups.Count -gt 0) { | |
| $latestBackup = $backups[0].FullName | |
| Copy-Item $latestBackup $cliPath -Force | |
| Write-Success "Restored from backup: $latestBackup" | |
| exit 0 | |
| } | |
| Write-Error "No backup files found" | |
| exit 1 | |
| } | |
| Write-Info "Found Claude Code at: $cliPath" | |
| Write-Host "" | |
| # Download acorn parser if needed | |
| $acornPath = Join-Path $env:TEMP "acorn-claude-fix.js" | |
| if (-not (Test-Path $acornPath)) { | |
| Write-Info "Downloading acorn parser..." | |
| try { | |
| Invoke-WebRequest -Uri "https://unpkg.com/[email protected]/dist/acorn.js" -OutFile $acornPath -UseBasicParsing | |
| } catch { | |
| Write-Error "Failed to download acorn parser" | |
| exit 1 | |
| } | |
| } | |
| # Create patch script | |
| $patchScript = @' | |
| const fs = require('fs'); | |
| const acornPath = process.argv[2]; | |
| const acorn = require(acornPath); | |
| const cliPath = process.argv[3]; | |
| const checkOnly = process.argv[4] === '--check'; | |
| let code = fs.readFileSync(cliPath, 'utf-8'); | |
| // Strip shebang (will restore later) | |
| let shebang = ''; | |
| if (code.startsWith('#!')) { | |
| const idx = code.indexOf('\n'); | |
| shebang = code.slice(0, idx + 1); | |
| code = code.slice(idx + 1); | |
| } | |
| // Check if already patched | |
| // Patch signature: .replace(/\\/g,"/") near -cwd related code | |
| const patchPattern = /\.replace\(\/\\\\\/g,["'"]\/["']\)/; | |
| if (patchPattern.test(code)) { | |
| // Further verify it's in the correct location | |
| const cwdIdx = code.indexOf('-cwd'); | |
| const replaceIdx = code.search(patchPattern); | |
| if (cwdIdx !== -1 && replaceIdx !== -1 && Math.abs(cwdIdx - replaceIdx) < 500) { | |
| console.log('ALREADY_PATCHED'); | |
| process.exit(2); | |
| } | |
| } | |
| // Parse JavaScript | |
| let ast; | |
| try { | |
| ast = acorn.parse(code, { ecmaVersion: 2022, sourceType: 'module' }); | |
| } catch (e) { | |
| console.error('PARSE_ERROR:' + e.message); | |
| process.exit(1); | |
| } | |
| // AST helper functions | |
| const src = (node) => code.slice(node.start, node.end); | |
| function findNodes(node, predicate, results = []) { | |
| if (!node || typeof node !== 'object') return results; | |
| if (predicate(node)) results.push(node); | |
| for (const key in node) { | |
| if (node[key] && typeof node[key] === 'object') { | |
| if (Array.isArray(node[key])) { | |
| node[key].forEach(child => findNodes(child, predicate, results)); | |
| } else { | |
| findNodes(node[key], predicate, results); | |
| } | |
| } | |
| } | |
| return results; | |
| } | |
| // Check if template literal contains specified text | |
| function templateContains(node, text) { | |
| if (node.type !== 'TemplateLiteral') return false; | |
| const raw = node.quasis.map(q => q.value.raw).join(''); | |
| return raw.includes(text); | |
| } | |
| // Check if node (including children) contains specified template text | |
| function containsTemplate(node, text) { | |
| const templates = findNodes(node, n => n.type === 'TemplateLiteral'); | |
| return templates.some(t => templateContains(t, text)); | |
| } | |
| // Find all functions (including arrow functions) | |
| const allFunctions = findNodes(ast, n => | |
| n.type === 'FunctionDeclaration' || | |
| n.type === 'FunctionExpression' || | |
| n.type === 'ArrowFunctionExpression' | |
| ); | |
| // 1. Find function containing "pwd -P >|" (bash exec function) | |
| let bashExecFunc = null; | |
| for (const fn of allFunctions) { | |
| if (containsTemplate(fn, 'pwd -P >|')) { | |
| bashExecFunc = fn; | |
| break; | |
| } | |
| } | |
| if (!bashExecFunc) { | |
| console.error('NOT_FOUND:bash_exec_function (pwd -P >|)'); | |
| process.exit(1); | |
| } | |
| console.log('FOUND:bash_exec_function at position ' + bashExecFunc.start); | |
| // 2. Find variable declaration containing "-cwd" in that function (H variable) | |
| // Pattern: H=J?wG0(F,`cwd-${K}`):wG0(V,`claude-${K}-cwd`) | |
| let targetVarDecl = null; | |
| let hDeclarator = null; | |
| const varDecls = findNodes(bashExecFunc, n => n.type === 'VariableDeclaration'); | |
| for (const decl of varDecls) { | |
| for (const d of decl.declarations) { | |
| if (!d.init) continue; | |
| // Check if it's a conditional expression containing -cwd template | |
| if (d.init.type === 'ConditionalExpression') { | |
| const hasCwd = containsTemplate(d.init, '-cwd') || | |
| containsTemplate(d.init, 'cwd-'); | |
| if (hasCwd) { | |
| targetVarDecl = decl; | |
| hDeclarator = d; | |
| break; | |
| } | |
| } | |
| } | |
| if (targetVarDecl) break; | |
| } | |
| if (!targetVarDecl || !hDeclarator) { | |
| console.error('NOT_FOUND:cwd_path_variable (claude-*-cwd or cwd-*)'); | |
| process.exit(1); | |
| } | |
| const hVarName = hDeclarator.id.name; | |
| console.log('FOUND:cwd_path_variable=' + hVarName + ' at position ' + targetVarDecl.start); | |
| // Output matched code snippet for verification | |
| const matchedCode = src(targetVarDecl); | |
| console.log('MATCHED_CODE:' + matchedCode.slice(0, 100) + (matchedCode.length > 100 ? '...' : '')); | |
| // 3. Find LQ function name (platform detection function) | |
| // Look for pattern like LQ() === "windows" | |
| let lqFuncName = null; | |
| const binaryExprs = findNodes(bashExecFunc, n => | |
| n.type === 'BinaryExpression' && | |
| (n.operator === '===' || n.operator === '==') && | |
| n.right?.type === 'Literal' && | |
| n.right?.value === 'windows' | |
| ); | |
| for (const expr of binaryExprs) { | |
| if (expr.left?.type === 'CallExpression' && expr.left.callee?.type === 'Identifier') { | |
| lqFuncName = expr.left.callee.name; | |
| break; | |
| } | |
| } | |
| if (!lqFuncName) { | |
| console.error('NOT_FOUND:platform_check_function (LQ)'); | |
| process.exit(1); | |
| } | |
| console.log('FOUND:platform_check_function=' + lqFuncName); | |
| // 4. Determine insert position | |
| // Insert after variable declaration statement ends (at semicolon) | |
| let insertPosition = targetVarDecl.end; | |
| // Check semicolon position | |
| const charAtEnd = code.charAt(insertPosition - 1); | |
| const charAfterEnd = code.charAt(insertPosition); | |
| console.log('CHAR_AT_END:' + JSON.stringify(charAtEnd)); | |
| console.log('CHAR_AFTER_END:' + JSON.stringify(charAfterEnd)); | |
| // If declaration doesn't end with semicolon but next char is semicolon, adjust position | |
| if (charAtEnd !== ';' && charAfterEnd === ';') { | |
| insertPosition++; | |
| } | |
| console.log('INSERT_POSITION:' + insertPosition); | |
| if (checkOnly) { | |
| console.log('NEEDS_PATCH'); | |
| process.exit(1); | |
| } | |
| // 5. Build patch code | |
| // Insert after variable declaration: if(LQ()==="windows")H=H.replace(/\\/g,"/"); | |
| const patchCode = `if(${lqFuncName}()==="windows")${hVarName}=${hVarName}.replace(/\\\\/g,"/");`; | |
| console.log('PATCH_CODE:' + patchCode); | |
| // 6. Apply patch | |
| const newCode = shebang + code.slice(0, insertPosition) + patchCode + code.slice(insertPosition); | |
| // 7. Backup original file | |
| const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); | |
| const backupPath = cliPath + '.backup-winpath-' + timestamp; | |
| fs.copyFileSync(cliPath, backupPath); | |
| console.log('BACKUP:' + backupPath); | |
| // 8. Write patched file | |
| fs.writeFileSync(cliPath, newCode); | |
| // 9. Verify patch | |
| const verifyCode = fs.readFileSync(cliPath, 'utf-8'); | |
| if (verifyCode.includes(patchCode)) { | |
| console.log('SUCCESS'); | |
| } else { | |
| console.error('VERIFY_FAILED'); | |
| fs.copyFileSync(backupPath, cliPath); | |
| process.exit(1); | |
| } | |
| '@ | |
| $tempPatchScript = Join-Path $env:TEMP "claude-winpath-patch-$PID.js" | |
| $patchScript | Out-File -FilePath $tempPatchScript -Encoding UTF8 | |
| # Run patch script | |
| $checkArg = if ($Check) { "--check" } else { "" } | |
| $output = & node $tempPatchScript $acornPath $cliPath $checkArg 2>&1 | |
| $exitCode = $LASTEXITCODE | |
| # Cleanup temp script | |
| Remove-Item $tempPatchScript -ErrorAction SilentlyContinue | |
| # Process output | |
| foreach ($line in $output) { | |
| switch -Regex ($line) { | |
| "^ALREADY_PATCHED" { Write-Success "Already patched"; exit 0 } | |
| "^PARSE_ERROR:(.+)" { Write-Error "Failed to parse cli.js: $($Matches[1])"; exit 1 } | |
| "^NOT_FOUND:(.+)" { Write-Error "Target code not found: $($Matches[1])"; exit 1 } | |
| "^FOUND:(.+)=(.+)" { Write-Info "Found $($Matches[1]): $($Matches[2])" } | |
| "^FOUND:(.+) at position (.+)" { Write-Info "Found $($Matches[1]) (position: $($Matches[2]))" } | |
| "^MATCHED_CODE:(.+)" { Write-Host " Matched code: " -NoNewline; Write-Host $Matches[1] -ForegroundColor DarkGray } | |
| "^CHAR_AT_END:(.+)" { Write-Host " Char at decl end: $($Matches[1])" -ForegroundColor DarkGray } | |
| "^CHAR_AFTER_END:(.+)" { Write-Host " Char after decl: $($Matches[1])" -ForegroundColor DarkGray } | |
| "^INSERT_POSITION:(.+)" { Write-Info "Insert position: $($Matches[1])" } | |
| "^PATCH_CODE:(.+)" { Write-Info "Patch code: $($Matches[1])" } | |
| "^NEEDS_PATCH" { | |
| Write-Host "" | |
| Write-Warning "Patch needed - run without -Check to apply" | |
| exit 1 | |
| } | |
| "^BACKUP:(.+)" { Write-Host ""; Write-Host "Backup: $($Matches[1])" } | |
| "^SUCCESS" { | |
| Write-Host "" | |
| Write-Success "Fix applied successfully!" | |
| Write-Host "" | |
| Write-Warning "Restart Claude Code for changes to take effect" | |
| } | |
| "^VERIFY_FAILED" { Write-Error "Verification failed, restoring backup..."; exit 1 } | |
| } | |
| } | |
| exit $exitCode |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
yes , but how to del the "nul" in my folder , i used the following command ,but it is not work
del "\?\C:\Users\HI.claude\skills\md-translator\nul"
Remove-Item -LiteralPath "\?\C:\Users\HI.claude\skills\md-translator\nul" -Force