-
-
Save Haleclipse/1c35fe7b98fe1b5ea0523eb4d3a10f0b to your computer and use it in GitHub Desktop.
| <# | |
| .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 |
Do you also use Windows? I created a patch that can solve the issue of slow file reading in the native Windows environment. On Linux, it takes 100ms to read a file, while on Windows it takes 2-3 seconds, with a large number of cygpatch commands executed every time a file is read. @Haleclipse https://github.com/caozhiyuan/claude-code-patch . there is issue anthropics/claude-code#13361
Thank you for your work, even though I'm using macOS.
I'll learn to review your code.
Claude Code is the buggiest product I've ever used; they probably don't have QA, haha!
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
Do you also use Windows? I created a patch that can solve the issue of slow file reading in the native Windows environment. On Linux, it takes 100ms to read a file, while on Windows it takes 2-3 seconds, with a large number of cygpatch commands executed every time a file is read.
@Haleclipse https://github.com/caozhiyuan/claude-code-patch . there is issue anthropics/claude-code#13361