[BUG] CLI crashes on empty shell variable substitution ${} - includes fix
Environment
- Platform (select one):
- [x ] Anthropic API
- [ ] AWS Bedrock
- [ ] Google Vertex AI
- [ ] Other: <!-- specify -->
- Claude CLI version: 1.0.30
- Operating System: 15.5 (24F74)
- Terminal: <!-- e.g. iTerm2, Terminal App -->
Bug Report: Claude Code CLI Crashes on Empty Shell Variable Substitution
Summary
The Claude Code CLI (version from npm) crashes with "Bad substitution: ${}" error when parsing shell commands containing empty variable substitutions.
Environment
- Platform: macOS (Darwin 24.5.0)
- Claude Code Installation: npm global install
- Path:
/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js
Error Details
Stack Trace
Error: Bad substitution: ${}
at E (file:///opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js:7:2123)
at file:///opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js:7:2863
at Array.map (<anonymous>)
at wm2 (file:///opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js:7:1942)
at Object.A (file:///opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js:7:3027)
Root Cause Analysis
The bug occurs in the shell command parser within the minified code. The specific problematic section:
if(T==="{"){
if(K+=1,J.charAt(K)==="}")
throw new Error("Bad substitution: "+J.slice(K-2,K+1));
// ... rest of parsing logic
}
When the parser encounters ${ followed immediately by }, it throws an error instead of handling it gracefully.
Reproduction Steps
- Use Claude Code CLI in a context where shell commands might contain
${} - The CLI will crash when attempting to parse such commands
Proposed Fix
Replace the error-throwing behavior with graceful handling of empty substitutions:
// Instead of:
if(K+=1,J.charAt(K)==="}")
throw new Error("Bad substitution: "+J.slice(K-2,K+1));
// Use:
if(K+=1,J.charAt(K)==="}")
return ""; // or handle as appropriate for empty substitution
Temporary Workaround
We've created a monkey-patch script that modifies the installed CLI file to handle empty substitutions gracefully. The patch:
- Locates the problematic code pattern in the minified file
- Replaces the error-throwing logic with a return of empty string
- Maintains all other functionality
Patch Implementation
// Find the pattern
const bugPattern = /if\(K\+=1,J\.charAt\(K\)==="}".*?throw new Error\("Bad substitution: "\+J\.slice\(K-2,K\+1\)\)/;
// Replace with
const fixedCode = 'if(K+=1,J.charAt(K)=="}"){O="",K+=1;return zm2(B,"",O)}';
Impact
This bug prevents Claude Code from functioning in environments where shell commands might contain empty variable substitutions, which can occur in various scripting contexts.
Recommendation
Update the shell parser to handle empty substitutions (${}) gracefully, either by:
- Treating them as empty strings
- Passing them through unchanged
- Providing a configuration option for the desired behavior
This would align with how many shell environments handle such cases and prevent unexpected crashes.
/**
- Patch script for Claude Code CLI to fix empty substitution bug
- This script patches the installed Claude Code CLI to handle empty
- variable substitutions (${}) gracefully instead of throwing an error.
- Usage: node patch-claude-code.js [restore]
*/
const fs = require('fs');
const path = require('path');
const CLI_PATH = '/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js';
const BACKUP_PATH = CLI_PATH + '.backup';
// The pattern that causes the bug - it throws an error on empty substitutions
const bugPattern = /if\(K\+=1,J\.charAt\(K\)==="}".*?throw new Error\("Bad substitution: "\+J\.slice\(K-2,K\+1\)\)/;
// The fix - handle empty substitutions gracefully
const fixedCode = 'if(K+=1,J.charAt(K)=="}"){O="",K+=1;return zm2(B,"",O)}';
function patchFile() {
console.log('Patching Claude Code CLI...');
// Check if file exists
if (!fs.existsSync(CLI_PATH)) {
console.error(Error: CLI file not found at ${CLI_PATH});
process.exit(1);
}
// Create backup if it doesn't exist
if (!fs.existsSync(BACKUP_PATH)) {
console.log('Creating backup...');
fs.copyFileSync(CLI_PATH, BACKUP_PATH);
console.log(Backup created at: ${BACKUP_PATH});
} else {
console.log('Backup already exists, skipping backup creation');
}
// Read the file
let content = fs.readFileSync(CLI_PATH, 'utf8');
// Check if already patched
if (!bugPattern.test(content)) {
console.log('File appears to be already patched or the bug pattern was not found');
return;
}
// Apply the patch
content = content.replace(bugPattern, fixedCode);
// Write the patched file
fs.writeFileSync(CLI_PATH, content, 'utf8');
console.log('✓ Patch applied successfully!');
console.log('The CLI will now handle empty substitutions (${}) gracefully.');
}
function restoreBackup() {
console.log('Restoring from backup...');
if (!fs.existsSync(BACKUP_PATH)) {
console.error('Error: No backup file found');
process.exit(1);
}
fs.copyFileSync(BACKUP_PATH, CLI_PATH);
console.log('✓ Original file restored successfully!');
}
// Main execution
const args = process.argv.slice(2);
if (args[0] === 'restore') {
restoreBackup();
} else {
patchFile();
}
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗