Something went wrong. Try again.
A simple patch that toggles claude-code thinking traces to be fully expanded by default without having to press ctrl + o
Something went wrong. Try again.
claude-code-patches patch-bash-dedup.js
7.5 kB · 199 lines
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200#!/usr/bin/env node// Patch for: https://github.com/anthropics/claude-code/issues/27621// Bash tool duplicates all output when a command exits non-zero.//// Root cause: in the bash tool's call(), M.stdout is passed as BOTH the stdout// AND the stderr argument to ShellError. FB8(ShellError) then returns// ["Exit code N", "", stderr, stdout] = ["Exit code N", "", M.stdout, M.stdout],// so the tool result content contains stdout twice.//// The bug is on the line that calls annotateStderrWithSandboxFailures:// let b = uA.annotateStderrWithSandboxFailures(A.command, X.stdout || "");// ^^^^^^^^^^^^^^^^// should be "" or M.stderr// throw new SC(X.stdout, b, X.code, X.interrupted);// ^ b == X.stdout, so ShellError.stderr == ShellError.stdout//// Fix: pass "" instead of X.stdout (the bash tool doesn't capture stderr separately;// stdout already contains merged output). This makes ShellError.stderr empty, so// FB8() returns ["Exit code N", "", "", X.stdout] and stdout appears only once.
const fs = require('fs');const path = require('path');const os = require('os');const { execSync } = require('child_process');
const args = process.argv.slice(2);const isDryRun = args.includes('--dry-run');const isRestore = args.includes('--restore');const showHelp = args.includes('--help') || args.includes('-h');
if (showHelp) { console.log('Claude Code Bash Output Deduplication Patcher v2.1.69'); console.log('======================================================\n'); console.log('Fixes: https://github.com/anthropics/claude-code/issues/27621'); console.log('When a bash command exits non-zero, its output appears twice in'); console.log('the tool result. This patch prevents the duplication.\n'); console.log('Usage: node patch-bash-dedup.js [options]\n'); console.log('Options:'); console.log(' --dry-run Preview changes without applying them'); console.log(' --restore Restore from backup file'); console.log(' --help, -h Show this help message\n'); process.exit(0);}
console.log('Claude Code Bash Output Deduplication Patcher v2.1.69');console.log('======================================================\n');
function safeExec(command) { try { return execSync(command, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim(); } catch (error) { return null; }}
function getClaudeCodePath() { const homeDir = os.homedir(); const attemptedPaths = [];
function checkPath(testPath, method) { if (!testPath) return null; attemptedPaths.push({ path: testPath, method }); try { if (fs.existsSync(testPath)) { try { return fs.realpathSync(testPath); } catch (e) { return testPath; } } } catch (e) {} return null; }
// Global npm install (most common on Linux servers) const globalRoot = safeExec('npm root -g'); if (globalRoot) { const found = checkPath(path.join(globalRoot, '@anthropic-ai', 'claude-code', 'cli.js'), 'npm root -g'); if (found) return found; }
// Standard system paths for (const p of [ '/usr/lib/node_modules/@anthropic-ai/claude-code/cli.js', '/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js', path.join(homeDir, '.claude', 'local', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'), path.join(homeDir, '.config', 'claude', 'local', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'), ]) { const found = checkPath(p, 'known paths'); if (found) return found; }
// From `which claude` binary const claudeBinary = safeExec('which claude'); if (claudeBinary) { try { const realBinary = fs.realpathSync(claudeBinary); const binDir = path.dirname(realBinary); const found = checkPath( path.join(binDir, '..', 'lib', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'), 'which claude' ); if (found) return found; } catch (e) {} }
getClaudeCodePath.attemptedPaths = attemptedPaths; return null;}
const targetPath = getClaudeCodePath();
if (!targetPath) { console.error('❌ Error: Could not find Claude Code installation'); process.exit(1);}
console.log(`Found Claude Code at: ${targetPath}\n`);
const backupPath = targetPath + '.backup';
if (isRestore) { if (!fs.existsSync(backupPath)) { console.error('❌ Error: Backup file not found at:', backupPath); process.exit(1); } console.log('Restoring from backup...'); fs.copyFileSync(backupPath, targetPath); console.log('✅ Restored successfully!'); console.log('\nPlease restart Claude Code for changes to take effect.'); process.exit(0);}
console.log('Reading cli.js...');let content = fs.readFileSync(targetPath, 'utf8');
// Bash Output Deduplication Patch// Fixes: annotateStderrWithSandboxFailures receiving M.stdout instead of M.stderr//// Variable names that change between CLI versions (find with: see update-thinking-patch skill):// uA = the sandbox object (xA in beautified)// A = tool input (A6 in beautified)// X = shell result M (M in beautified)// SC = ShellError class (aS in beautified)// J = return code interpretation (J in beautified)// g = user-interrupt flag//// To find the new pattern after a version upgrade:// grep -o '.\{30\}annotateStderrWithSandboxFailures.\{80\}' /path/to/cli.js//// Note: In v2.1.69, the pattern is: uA.annotateStderrWithSandboxFailures(A.command,X.stdout||"")const bashDedupSearchPattern = 'annotateStderrWithSandboxFailures(A.command,X.stdout||"")';const bashDedupReplacement = 'annotateStderrWithSandboxFailures(A.command,"")';
console.log('Checking patch...\n');console.log('Bash output deduplication patch:');
let patchApplied = false;
if (content.includes(bashDedupSearchPattern)) { patchApplied = true; console.log(' ✅ Pattern found - ready to apply');} else if (content.includes(bashDedupReplacement)) { console.log(' ⚠️ Already applied');} else { console.log(' ❌ Pattern not found - variable names may have changed in this version'); console.log(' Run: grep -o \'.\\{30\\}annotateStderrWithSandboxFailures.\\{80\\}\' ' + targetPath); console.log(' Then update bashDedupSearchPattern in this script. See update-thinking-patch skill.');}
if (isDryRun) { console.log('\n📋 DRY RUN - No changes will be made\n'); console.log(`Bash deduplication patch: ${patchApplied ? 'WOULD APPLY' : 'SKIP'}`); if (patchApplied) console.log('\nRun without --dry-run to apply patch.'); process.exit(0);}
if (!patchApplied) { console.error('\n❌ No patch to apply. Pattern not found or already applied.'); process.exit(1);}
if (!fs.existsSync(backupPath)) { console.log('\nCreating backup...'); fs.copyFileSync(targetPath, backupPath); console.log(`✅ Backup created: ${backupPath}`);}
console.log('\nApplying patch...');content = content.replace(bashDedupSearchPattern, bashDedupReplacement);console.log('✅ Bash output deduplication patch applied');
console.log('\nWriting patched file...');fs.writeFileSync(targetPath, content, 'utf8');console.log('✅ File written successfully');
console.log('\n🎉 Patch applied! Please restart Claude Code for changes to take effect.');console.log('\nTo restore original behavior, run: node patch-bash-dedup.js --restore');process.exit(0);