Skip to content

[Issue] Extension fails with ENAMETOOLONG error on large git diffs #5

@DawidGrzejek

Description

@DawidGrzejek

#4

Issue: Extension fails with ENAMETOOLONG error on large git diffs

Description

When attempting to generate commit messages for large git diffs, the extension fails with the error:

Error: spawn ENAMETOOLONG Claude CLI execution failed: spawn ENAMETOOLONG

This occurs because the extension passes the entire git diff as a base64-encoded command-line argument, which can exceed the OS command-line length limit (~8KB on most systems).

Steps to Reproduce

  1. Make changes to your repository that result in a large git diff (e.g., >50KB)
  2. Stage the changes with git add
  3. Click the sparkle icon in VS Code's Source Control panel to generate a commit message
  4. The extension fails with ENAMETOOLONG error

Environment

  • OS: All platforms (Windows, Linux, macOS)
  • Extension Version: 1.0.0
  • VS Code Version: Any

Root Cause

The extension currently uses inline command-line arguments to pass the prompt to Claude CLI:

echo "very_long_base64_string" | base64 -d | claude --print --model sonnet --output-format json

When the diff is large, the base64-encoded string exceeds the command-line argument length limit, causing the ENAMETOOLONG error.

Proposed Solution

Use a temporary file to store the prompt when it exceeds a safe length threshold:

  1. Check if the prompt length exceeds a threshold (e.g., 100KB)
  2. If yes, write the prompt to a temporary file
  3. Pipe the file contents to Claude CLI instead of using inline arguments
  4. Clean up the temporary file after execution

Example Implementation

// For large prompts, use a temp file to avoid command-line length limits
let command;
let tempFile: string | null = null;
const maxInlineLength = 100000; // Conservative limit

if (prompt.length > maxInlineLength) {
    // Create temp file with the prompt
    tempFile = path.join(os.tmpdir(), `claude-prompt-${Date.now()}.txt`);
    fs.writeFileSync(tempFile, prompt, 'utf8');
    
    // Read from temp file and pipe to claude
    command = `cat '${tempFile}' | ${this.claudePath}`;
} else {
    // For shorter prompts, use base64 encoding inline
    const base64Prompt = Buffer.from(prompt, 'utf8').toString('base64');
    command = `echo "${base64Prompt}" | base64 -d | ${this.claudePath}`;
}

// Add flags
command += ' --print --model sonnet --output-format json --dangerously-skip-permissions';

// ... execute command ...

// Cleanup in finally block
if (tempFile) {
    fs.unlinkSync(tempFile);
}

Benefits

  • ✅ Works with git diffs of any size
  • ✅ Backward compatible (small diffs still use inline base64)
  • ✅ Cross-platform solution
  • ✅ Automatic cleanup of temporary files

Additional Context

This issue affects all platforms and can occur whenever a commit includes extensive changes (e.g., adding large dependencies, generated files, or refactoring many files at once).

Quick fix to copiled .js file

1. Find the executeCommand methodLook for the section that builds the command (around line 280-290 in the .js file)

2. Replace the command building logic

Find this:
const base64Prompt = Buffer.from(prompt, 'utf8').toString('base64');
let command = `echo "${base64Prompt}" | base64 -d | ${this.claudePath}`;
Replace with:
// Detect platform
const isWindows = process.platform === 'win32';

// For large prompts, use a temp file
const fs = require('fs');
const path = require('path');
const os = require('os');
let command;
let tempFile = null;
const maxInlineLength = isWindows ? 7000 : 100000;

if (prompt.length > maxInlineLength) {
    // Create temp file with the prompt
    tempFile = path.join(os.tmpdir(), `claude-prompt-${Date.now()}.txt`);
    fs.writeFileSync(tempFile, prompt, 'utf8');

    if (isWindows) {
        command = `Get-Content -Path '${tempFile}' -Raw | ${this.claudePath}`;
    } else {
        command = `cat '${tempFile}' | ${this.claudePath}`;
    }
} else {
    const base64Prompt = Buffer.from(prompt, 'utf8').toString('base64');
    
    if (isWindows) {
        command = `[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${base64Prompt}')) | ${this.claudePath}`;
    } else {
        command = `echo "${base64Prompt}" | base64 -d | ${this.claudePath}`;
    }
}

3. Update execOptions

Find this:
shell: '/bin/bash',
Replace with:
shell: isWindows ? 'powershell.exe' : '/bin/bash',
Find this:
PATH: `/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${process.env.PATH}:${process.env.HOME}/.nvm/versions/node/*/bin`

Replace with:

PATH: isWindows ? process.env.PATH : `/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${process.env.PATH}:${process.env.HOME}/.nvm/versions/node/*/bin`

4. Add cleanup after the catch block

Find the end of the catch block:

catch (error) {
    // ... error handling
    throw new Error(`Claude CLI execution failed: ${error.message}`);
}

Add after it:

finally {
    // Clean up temp file if it was created
    if (tempFile) {
        try {
            fs.unlinkSync(tempFile);
        } catch (cleanupError) {
            // Ignore cleanup errors
        }
    }
}

5. Skip bash/zsh detection on Windows

Find (around line 93-120):
// Try using bash shell

Wrap the entire bash shell section with:

if (process.platform !== 'win32') {
    // ... existing bash shell code
}

Do the same for the zsh shell section (around line 120-145)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions