[BUG] CLI crashes on empty shell variable substitution ${} - includes fix

Resolved 💬 2 comments Opened Jun 20, 2025 by acoliver Closed Jul 20, 2025

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

  1. Use Claude Code CLI in a context where shell commands might contain ${}
  2. 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:

  1. Locates the problematic code pattern in the minified file
  2. Replaces the error-throwing logic with a return of empty string
  3. 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:

  1. Treating them as empty strings
  2. Passing them through unchanged
  3. 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();
}

README.md

claude-code-bug-analysis.md

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗