claude-code icon indicating copy to clipboard operation
claude-code copied to clipboard

[BUG] Complex bash syntax fails with preprocessing (reproducible, workaround exists but inconsistent)

Open binaryphile opened this issue 2 months ago โ€ข 10 comments

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Complex bash patterns involving pipes fail with silent data loss or syntax errors. Loop variables are silently stripped when piped, causing commands to produce wrong output with no error message. Command substitution with pipes causes syntax errors. JavaScript template literals in heredocs are rejected as "Bad substitution" errors.

This appears to be a regression of the v1.0.77 fix for "heredoc and multiline string escaping." The workaround (bash -c) is documented in Issue #774 but practically unusable - even Claude Code itself can't consistently apply it when generating commands.

Related to issues #9323, #8318, and #11182, which appear to be different manifestations of the same preprocessing issue.

What Should Happen?

Valid bash syntax should execute correctly:

  • Loop variables should retain their values when piped
  • Command substitution with pipes should work
  • Heredocs with non-bash syntax (like JavaScript) should be treated as literal text
  • Multi-line loops should preserve newlines

These patterns work in standard terminals and when wrapped in bash -c, indicating they're valid bash syntax being incorrectly rejected or transformed by preprocessing.

Error Messages/Logs

# Symptom 1: Silent data loss (CRITICAL)
$ for i in one two three; do echo "Item: $i" | cat; done
Item:
Item:
Item:

# Variables completely stripped, no error message

# Symptom 2: Command substitution syntax error
$ result=$(echo "test" | tr a-z A-Z); echo "Result: $result"
bash: -c: line 1: syntax error near unexpected token `|'

# Symptom 3: JavaScript template literal rejection
$ cat <<'EOF'
return `<div>${escapeHtml(cmd)}</div>`
EOF
Failed to parse command: Bad substitution: escapeHtml

# Symptom 4: Variable cleared by pipe
$ export TEST_VAR=alpha && echo "$TEST_VAR" | hexdump -C
00000000  0a                                                |.|
00000001
# Only shows newline (0a), "alpha" completely gone

Steps to Reproduce

Primary reproduction (CRITICAL silent data loss):

  1. Run this command:
for i in one two three; do echo "Item: $i" | cat; done
  1. Expected output:
Item: one
Item: two
Item: three
  1. Actual output:
Item:
Item:
Item:
  1. Verification: Run same command 3 times - get identical wrong output (100% reproducible)

  2. Workaround that works:

bash -c 'for i in one two three; do echo "Item: $i" | cat; done'

Produces correct output with variables intact.

Additional reproductions:

Command substitution with pipes:

result=$(echo "test" | tr a-z A-Z); echo "Result: $result"

Error: bash: -c: line 1: syntax error near unexpected token '|'

JavaScript in heredoc:

cat <<'EOF'
return `<div>${escapeHtml(cmd)}</div>`
EOF

Error: Failed to parse command: Bad substitution: escapeHtml

Variable cleared by pipe:

export TEST_VAR=alpha && echo "$TEST_VAR" | wc -c

Expected: 6, Actual: 1 (verified with hexdump - only newline remains)

All patterns fixed by bash -c workaround.

Claude Model

Sonnet (default)

Is this a regression?

Yes, this worked in a previous version

Last Working Version

v1.0.77 (CHANGELOG claimed "Fixed heredoc and multiline string escaping" but issue persists or regressed in v2.0.28. Issue #4315 filed July 2025 after claimed fix.)

Claude Code Version

v2.0.28

Platform

Anthropic API

Operating System

Ubuntu/Debian Linux

Terminal/Shell

Other

Additional Information

Note: this was written with assistance from Claude Code. The "we" refers to the two of us.

Related Issues (Reproduced Identically)

  • #9323 - JavaScript template literals in heredocs rejected
  • #8318 - Environment variables cleared when piped
  • #11182 - For loop newlines stripped causing syntax errors

Note on scope: While we document multiple symptoms, this is a single bug about preprocessing being too aggressive on heredoc/multiline/escaping. All symptoms share:

  • Common root cause (preprocessing transformations)
  • Same workaround (bash -c)
  • Same pattern (pipes and complex substitutions affected)
  • Deterministic behavior

Severity Assessment

CRITICAL (3 symptoms):

  1. Silent data loss - Loop variables stripped with no error (Symptom 1)
  2. Command substitution broken - $(cmd | cmd) completely non-functional (Symptom 2)
  3. Variable clearing - Export vars disappear in pipes with no error (Symptom 4)

HIGH (2 symptoms):

  • For loop newlines stripped โ†’ syntax errors
  • Command groups { ... } treated as literal commands

MEDIUM (1 symptom):

  • False positive security rejection of valid JavaScript in config files

Observations

Behavior suggests sophisticated processing:

  • Context-aware: cat <<'EOF'\nrm -rf /\nEOF works correctly - understands dangerous commands are safe in quoted heredocs
  • Parses syntax: Error "Bad substitution: escapeHtml" extracts function name from ${escapeHtml(...)}
  • Deterministic: Variation tests (same command 3x) produced identical results every time
  • Pre-bash transformation: Error messages reference mangled syntax (escaped \$), indicating transformation before bash sees command

Consistent failure patterns:

  • All failures involve pipes OR complex substitutions
  • Simple commands without pipes work
  • All fixed by bash -c wrapper (also sh -c, dash -c, eval)
  • Error format: "Failed to parse command: Bad substitution: FUNCTION_NAME"

What Works (Scope Narrowing)

  • Simple heredocs without complex substitutions
  • Basic pipes without loops or command substitution
  • C-style for loops: for ((i=0; i<3; i++)); do echo $i; done
  • Commands without pipes generally work

Workarounds

Primary workaround: bash -c 'script' (from Issue #774)

  • Tested against all failing patterns: 100% success rate
  • Also works: sh -c, dash -c, eval, pipes to bash

Why unusable in practice:

During this investigation, Claude Code itself repeatedly failed to apply its own workaround when generating commands - even while being keenly aware of it due to being in the process of documenting the very issue. This creates a cycle:

  1. Ask Claude Code to run complex bash command
  2. It generates direct bash (forgetting workaround)
  3. Command fails with preprocessing error
  4. Must remind Claude Code to use bash -c
  5. Repeat for every complex command

If Claude Code can't reliably apply this, human users face even more difficulty:

  • Remembering which patterns need it (not obvious from syntax)
  • Complex quoting transformations: <<'EOF' โ†’ bash -c 'cat <<'\''EOF'\'''
  • Constant vigilance

Discoverability: Workaround only found through Issue #774 - not obvious to users encountering errors.

Impact

User impact:

  • At least 3 other users reported identical symptoms (issues #9323, #8318, #11182)
  • Silent data loss bugs (no error, wrong output) can be dangerous
  • Command substitution with pipes completely non-functional
  • Workaround exists but practically inconsistent

Organizational adoption impact:

I'm the engineer championing Claude Code adoption in our mid-sized organization. If I can't get this addressed, it will seriously impair my ability to advocate for broader adoption.

This isn't pressure - I want to recommend Claude Code. But when basic bash patterns fail unpredictably, it undermines confidence. Other engineers ask "why does this loop work here but fail there?" and I don't have good answers.

External Documentation

According to publicly documented architecture analysis (Shatrov, K. 2025), the Bash tool performs preprocessing with up to two passes before execution. Our observations align with this documented behavior.

Reference: Shatrov, K. (2025). Reverse engineering Claude Code. https://kirshatrov.com/posts/claude-code-internals

Possible Approaches (Suggestions, Not Prescriptions)

We don't know your constraints or security requirements, but wanted to offer possibilities:

  1. Refine detection rules - Reduce false positives while maintaining security

    • Pro: Better precision, fewer legitimate commands blocked
    • Con: Complex to implement, risk of bypasses
  2. User opt-out flag - Like dangerouslyDisableSandbox parameter

    • Pro: Explicit user choice, maintains security by default
    • Con: Support burden, user education, potential misuse
  3. Better error messages - Explain preprocessing and suggest bash -c workaround

    • Pro: Improves discoverability, lower implementation risk
    • Con: Doesn't fix underlying issue, workaround still hard to use
  4. Graduated strictness - Simple commands lenient, complex strict

    • Pro: Balance safety and usability
    • Con: Complex implementation, defining "simple" vs "complex" challenging
  5. Teach Claude Code the workaround - Update system prompt for bash -c usage

    • Pro: Would make workaround more usable
    • Con: Still a workaround, doesn't fix root cause

We aren't the judge of what's feasible given your security requirements.

Acknowledgments

Positive observations:

  • Team engaged constructively on Issue #774 (@dicksontsai)
  • Appreciate security focus (CVE-2025-54795 fix demonstrates commitment)
  • Understand this is difficult security vs usability tradeoff
  • Issue #4315 (heredoc mangling) open since July 2025 shows active work in this area

Complexity recognition:

We recognize that balancing security (preventing command injection) with usability (supporting legitimate bash patterns) is extremely challenging. We're reporting these edge cases as data points to help improve the system, not as criticism.

The fact that preprocessing is context-aware (understands quoted heredocs, parses syntax) shows sophisticated engineering. These issues appear to be overly aggressive rules rather than fundamental flaws.

Offer to Help

Happy to:

  • Test any proposed fixes or experimental builds
  • Provide additional reproduction cases
  • Clarify any findings
  • Try different bash versions/environments for debugging
  • Provide more detailed traces/logs if diagnostic information available

Thank you for your work on Claude Code and for considering this report.

binaryphile avatar Nov 07 '25 21:11 binaryphile

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/8318
  2. https://github.com/anthropics/claude-code/issues/9323
  3. https://github.com/anthropics/claude-code/issues/11182

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and ๐Ÿ‘ the existing issue instead
  • To prevent auto-closure, add a comment or ๐Ÿ‘Ž this comment

๐Ÿค– Generated with Claude Code

github-actions[bot] avatar Nov 07 '25 21:11 github-actions[bot]

This is not a duplicate - it's a comprehensive analysis showing these 3 issues (#8318, #9323, #11182) plus additional symptoms from #7387 all stem from the same root cause in bash preprocessing.

Previous reporters didn't have the full picture. This report:

  • Reproduces all existing issues identically (verified with exact error messages)
  • Connects the dots: proves all symptoms share the same preprocessing root cause
  • Proves deterministic behavior through variation testing (100% reproducible)
  • Identifies this as a regression of v1.0.77 "heredoc and multiline string escaping" fix
  • Provides systematic evidence and unified workaround analysis

Closing this as duplicate would lose the unified analysis showing the common pattern. The individual issues are symptoms; this identifies the disease.

binaryphile avatar Nov 08 '25 13:11 binaryphile

Additional Reproduction: Multi-line for loop with pipe

Found another manifestation of this preprocessing bug:

Reproduction

for pr in 161; do
  echo "test" | cat
done

Error:

/bin/bash: eval: line 2: syntax error: unexpected end of file

Root Cause

Adding set -x reveals what's actually being eval'd:

set -x for pr in 161 ; do echo test < /dev/null | cat done

The preprocessing:

  1. Collapses newlines to spaces
  2. Attempts to insert semicolons
  3. Fails to insert semicolon after piped commands

Should be: ... | cat; done Actually is: ... | cat done

Workaround

Use heredoc syntax to bypass eval preprocessing:

bash <<'BASH'
for pr in 161; do
  echo "test" | cat
done
BASH

This works reliably and is now documented in my local ~/.claude/must-read-before.d/using-claude-code-tool/Bash.md guide.

Pattern

Single-line version works fine: for pr in 161; do echo "test" | cat; done

Only fails when multi-line + contains pipe.

Same root cause as your reported symptoms - preprocessing trying to normalize bash syntax without proper parsing.

bukzor avatar Nov 14 '25 23:11 bukzor

This is a really frustrating bug, because claude seems to default to this syntax for most operations, which always fails, and it struggles mightily in righting itself after the failure.

ChadNedzlek avatar Nov 26 '25 05:11 ChadNedzlek

PreToolUse Hook Workaround for Bash Preprocessing Bugs

Created a hook that fixes all 4 known bash preprocessing bugs in Claude Code by wrapping problematic commands in bash -c '...'.

ZERO TOKEN OVERHEAD - hooks run externally before command execution, no context consumed.


GitHub Issues Fixed

Issue Problem Link
#11225 $(...) command substitution mangled View
#11182 Multi-line commands have newlines stripped View
#8318 Loop variables silently cleared with pipes View
#10014 For-loop variable expansion issues View

How It Works

The hook intercepts Bash tool calls before execution and:

  1. Detects problematic patterns:

    • $(...) command substitution outside single quotes
    • Multi-line commands (contain \n)
    • Loops with pipes (for ... | ... or | while ...)
  2. Wraps in bash -c '...' to bypass preprocessing:

    • Escapes single quotes properly (' โ†’ '\'')
    • Preserves newlines naturally inside the quoted string
    • Skips already-wrapped commands
  3. Handles special cases:

    • Heredocs (<<) - skips continuation fixing (heredocs handle newlines correctly)
    • Control structures (if/then, for/do, while/do, case/in) - skips continuation fixing (newlines are intentional statement separators)
    • Quoted strings - doesn't add continuations inside quotes

Test Results

Test Suite Tests Pass
Pattern detection tests 139 100%
Execution tests 45 100%
Edge case tests 29 100%
Adversarial tests 60 100%
JSON format tests 26 100%
Total 299 100%

Includes regression tests for:

  • Nested control structures (for โ†’ if โ†’ if)
  • Mixed control structures (for โ†’ while โ†’ if)
  • Case statements with indented bodies
  • Commands with line continuations

Installation

Step 1: Save the hook

mkdir -p ~/.claude/hooks
cat > ~/.claude/hooks/fix-bash-substitution.py << 'EOF'
#!/usr/bin/env python3
"""
PreToolUse hook to fix bash command mangling in Claude Code.

Wraps complex commands in `bash -c '...'` to bypass preprocessing bugs.

Handles:
  - Command substitution $(...) - GitHub Issue #11225
  - Multi-line commands (newlines) - GitHub Issue #11182
  - Loops with pipes (for/while + |) - GitHub Issue #8318

See: https://github.com/anthropics/claude-code/issues/11225

Version: 5 (2025-12-10)
"""
import json
import re
import sys


def has_control_structures(command: str) -> bool:
    """Check if command contains bash control structures.

    If control structures are present, we should NOT add continuations
    because the newlines are intentional statement separators, not
    broken continuations.
    """
    control_patterns = [
        r'\bif\b.*\bthen\b',      # if...then
        r'\bfor\b.*\bdo\b',       # for...do
        r'\bwhile\b.*\bdo\b',     # while...do
        r'\buntil\b.*\bdo\b',     # until...do
        r'\bcase\b.*\bin\b',      # case...in
        r'\bfunction\b',          # function definition
        r'^\s*\w+\s*\(\)\s*\{',   # func() { definition
    ]
    for pattern in control_patterns:
        if re.search(pattern, command, re.MULTILINE):
            return True
    return False


def fix_continuations(command: str) -> str:
    """Quote-aware, control-structure-aware continuation fixing.

    For simple multi-line commands (like curl with -H flags on separate lines),
    adds backslash continuations so bash -c treats them as one command.

    Skips:
    - Commands without newlines
    - Commands with control structures (newlines are intentional)
    - Newlines inside quoted strings
    """
    if '\n' not in command:
        return command

    # Don't add continuations to control structures
    if has_control_structures(command):
        return command

    result = []
    in_single_quote = False
    in_double_quote = False
    i = 0

    while i < len(command):
        char = command[i]

        # Track quote state (respecting escapes)
        if char == "'" and not in_double_quote:
            if i == 0 or command[i-1] != '\\':
                in_single_quote = not in_single_quote
        elif char == '"' and not in_single_quote:
            if i == 0 or command[i-1] != '\\':
                in_double_quote = not in_double_quote

        # Add continuation before newline if:
        # 1. Outside quotes
        # 2. Next char is whitespace (continuation pattern)
        # 3. Not already escaped
        if char == '\n':
            in_quotes = in_single_quote or in_double_quote
            next_is_whitespace = (i + 1 < len(command) and command[i + 1] in ' \t')
            prev_is_backslash = (i > 0 and command[i-1] == '\\')

            if not in_quotes and next_is_whitespace and not prev_is_backslash:
                result.append(' \\')

        result.append(char)
        i += 1

    return ''.join(result)


def needs_wrapping(command: str) -> bool:
    """Detect commands that might be mangled by preprocessing."""
    stripped = command.strip()

    # Already wrapped - skip
    if stripped.startswith("bash -c ") or stripped.startswith("bash -c'"):
        return False

    # 1. Command substitution $(...) outside single quotes
    in_single_quote = False
    i = 0
    while i < len(command):
        char = command[i]
        if char == "'" and (i == 0 or command[i-1] != '\\'):
            in_single_quote = not in_single_quote
        elif not in_single_quote and char == '$' and i + 1 < len(command) and command[i+1] == '(':
            return True
        i += 1

    # 2. Multi-line commands (newlines get stripped/mangled)
    if '\n' in command:
        return True

    # 3. Loops with pipes (variables silently cleared)
    if re.search(r'\b(for|while|until)\b.*\|', command):
        return True
    if re.search(r'\|.*\b(while|until)\b', command):
        return True

    return False


def main():
    try:
        input_data = json.load(sys.stdin)
    except json.JSONDecodeError:
        sys.exit(1)

    command = input_data.get("tool_input", {}).get("command", "")

    if input_data.get("tool_name") != "Bash" or not command:
        sys.exit(0)

    if not needs_wrapping(command):
        sys.exit(0)

    # Fix continuations (skip for heredocs - they handle newlines correctly)
    if '<<' in command:
        fixed = command
    else:
        fixed = fix_continuations(command)

    # Escape single quotes for bash -c '...'
    escaped = fixed.replace("'", "'\\''")

    print(json.dumps({
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "allow",
            "updatedInput": {"command": f"bash -c '{escaped}'"}
        }
    }))
    sys.exit(0)


if __name__ == "__main__":
    main()
EOF

Step 2: Make executable

chmod +x ~/.claude/hooks/fix-bash-substitution.py

Step 3: Configure Claude Code

In Claude Code, run:

/hooks

Then:

  1. Select Add hook
  2. Select PreToolUse
  3. Select Bash as the tool matcher
  4. Enter path: ~/.claude/hooks/fix-bash-substitution.py

Step 4: Restart session

Start a new Claude Code session for the hook to take effect.


Verifying It Works

Test with a command that would normally fail:

# This should work (command substitution)
echo "Today is $(date +%Y-%m-%d)"

# This should work (multi-line)
curl https://api.example.com \
    -H "Accept: application/json" \
    -H "Authorization: Bearer token"

# This should work (loop with pipe)
for i in 1 2 3; do echo $i | cat; done

If the hook is working, you'll see commands wrapped in bash -c '...' in the execution output.


Troubleshooting

Hook not running?

  • Check file is executable: ls -la ~/.claude/hooks/fix-bash-substitution.py
  • Check hook is configured: /hooks in Claude Code
  • Restart Claude Code session

Still getting errors?

  • Check hook output: The hook prints JSON to stdout when it modifies a command
  • Test manually: echo '{"tool_name":"Bash","tool_input":{"command":"echo $(date)"}}' | python3 ~/.claude/hooks/fix-bash-substitution.py

Changelog

Version Date Changes
v5 2025-12-10 Skip continuation-fixing for control structures (fixes nested if/for/while)
v4 2025-12-10 Heredoc detection - skip continuation fixing for <<
v3 2025-12-10 Quote-aware continuation fixing
v2 2025-12-09 Added loop-with-pipe detection
v1 2025-12-09 Initial release - command substitution fix

smconner avatar Dec 09 '25 10:12 smconner

The hook solution is a great idea. Prior to your improved version that handled different syntax, I put together my own solution (inspired by yours). Either alternative should work for folks hopefully. The differences with mine were a) Go as the implementation language and b ) slightly different wrapping approach.

I have a preference for Go tools due to speed and lack of deployment dependencies.

The alternate approach is to handle tricky edge cases with quoting when using bash -c. The tool avoids this by base64 encoding the command and decoding it in the resulting shell. This avoids all edge cases.

I offer it with thanks to you, @smconner, as an alternative to the python version: https://github.com/binaryphile/claude-code-bash-tool-hook

binaryphile avatar Dec 10 '25 16:12 binaryphile

@binaryphile much appreciated! Thank you for the kind words! I've never actually used Go, but I'll check out your version now. I'll put together some comparison tests using the set of pressure tests and edgecase tests I used to develop mine.

[UPDATE]

@binaryphile you inspired me to also create a GitHub repo so you can see in more detail what tests I did too: https://github.com/smconner/claude-code-bash-hook

smconner avatar Dec 10 '25 22:12 smconner

๐Ÿ”ฌ Claude Code Bash Hook Comparison

v5 Python (Quote Escaping) vs Go (Base64 Encoding)

A comprehensive analysis of two approaches to fixing Claude Code's bash preprocessing bugs.

Tested: December 11, 2025


๐Ÿ“‹ Executive Summary

Metric v5 Python Go base64 Winner
Correctness 66/66 (100%) 64/66 (97%) ๐Ÿ† v5
Execution Speed +0.2ms avg +2.4ms avg ๐Ÿ† v5
Character Overhead +50 chars +550 chars ๐Ÿ† v5
Wrapping Speed 86.9 ยตs 7.0 ยตs ๐Ÿ† Go
Simplicity ~180 lines ~75 lines ๐Ÿ† Go

Bottom line: Both hooks successfully fix Claude Code's preprocessing bugs. The v5 Python hook has perfect correctness and lower runtime overhead, while the Go hook is simpler and faster to process.


๐Ÿ—๏ธ Architectural Comparison

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                           v5 PYTHON HOOK                                    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Input: echo $(date)                                                        โ”‚
โ”‚                                                                             โ”‚
โ”‚  1. Detect: Has $()? โ†’ YES                                                  โ”‚
โ”‚  2. Fix continuations (if needed)                                           โ”‚
โ”‚  3. Escape: ' โ†’ '\''                                                        โ”‚
โ”‚  4. Wrap: bash -c 'echo $(date)'                                            โ”‚
โ”‚                                                                             โ”‚
โ”‚  Philosophy: Selective wrapping + repair broken continuations               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                             GO HOOK (base64)                                โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚  Input: echo $(date)                                                        โ”‚
โ”‚                                                                             โ”‚
โ”‚  1. Detect: Is it empty or already wrapped? โ†’ NO                            โ”‚
โ”‚  2. Encode: base64("echo $(date)") โ†’ "ZWNobyAkKGRhdGUp"                     โ”‚
โ”‚  3. Wrap: bash -c "$(echo 'ZWNobyAkKGRhdGUp' | base64 -d)"                  โ”‚
โ”‚                                                                             โ”‚
โ”‚  Philosophy: Wrap everything, let base64 handle all edge cases              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Key Design Differences

Aspect v5 Python Go base64
Trigger Only wraps commands with $(), newlines, or loop+pipe Wraps ALL commands
Encoding Quote escaping (' โ†’ '\'') Base64 encoding
Continuation Repair โœ… Adds missing \ backslashes โŒ Preserves as-is
Runtime Cost Single bash -c Subshell + pipe + base64 -d

โœ… Correctness Testing

Test Suites

Suite Tests Description
Base Tests 26 Core patterns from GitHub issues
Execution Tests 16 Real command execution validation
Adversarial Tests 24 Edge cases: quotes, unicode, escapes
Total 66

Results

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘                    CORRECTNESS RESULTS                            โ•‘
โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
โ•‘  Test Suite          โ”‚  v5 Python       โ”‚  Go base64              โ•‘
โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
โ•‘  Base Tests (26)     โ”‚  26/26 (100%) โœ“  โ”‚  25/26 (96.2%)          โ•‘
โ•‘  Execution (16)      โ”‚  16/16 (100%) โœ“  โ”‚  15/16 (93.8%)          โ•‘
โ•‘  Adversarial (24)    โ”‚  24/24 (100%) โœ“  โ”‚  24/24 (100%) โœ“         โ•‘
โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ชโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
โ•‘  TOTAL               โ”‚  66/66 (100%) ๐Ÿ† โ”‚  64/66 (97.0%)          โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•งโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Go Hook Failures

Both failures stem from the same root cause โ€” broken line continuations:

# Original command (Claude Code stripped the backslashes):
echo start
    -H 'Accept: application/json'
    -H 'Authorization: Bearer token'
    https://api.example.com

# v5 Python output (PASS):
# Adds backslash continuations โ†’ single echo command
echo start \
    -H 'Accept: application/json' \
    -H 'Authorization: Bearer token' \
    https://api.example.com

# Go base64 output (FAIL):
# Preserves newlines โ†’ bash interprets as 4 separate commands
start                           # โ† echo outputs this
bash: -H: command not found     # โ† "-H" is not a command
bash: -H: command not found
bash: https://...: No such file or directory

Why this matters: This is a real failure mode when Claude generates multi-line curl or similar commands. Claude Code's preprocessing strips the \ continuations, leaving broken syntax that needs repair, not just preservation.


โšก Performance Testing

Wrapping Speed (Hook Processing Time)

How long does each hook take to process a command? (1000 iterations)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Command            โ”‚ v5 Python       โ”‚ Go base64       โ”‚ Ratio      โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ simple_echo        โ”‚      1.94 ยตs    โ”‚      0.61 ยตs    โ”‚    3.2x    โ”‚
โ”‚ simple_ls          โ”‚      1.94 ยตs    โ”‚      0.63 ยตs    โ”‚    3.1x    โ”‚
โ”‚ subst_simple       โ”‚      0.93 ยตs    โ”‚      0.65 ยตs    โ”‚    1.4x    โ”‚
โ”‚ subst_pipe         โ”‚      1.35 ยตs    โ”‚      0.70 ยตs    โ”‚    1.9x    โ”‚
โ”‚ loop_pipe          โ”‚      3.87 ยตs    โ”‚      0.69 ยตs    โ”‚    5.6x    โ”‚
โ”‚ multiline          โ”‚     14.21 ยตs    โ”‚      0.69 ยตs    โ”‚   20.5x    โ”‚
โ”‚ large (500 char)   โ”‚     59.45 ยตs    โ”‚      1.69 ยตs    โ”‚   35.2x    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ TOTAL              โ”‚     86.90 ยตs    โ”‚      7.03 ยตs    โ”‚   12.4x ๐Ÿ† โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Winner: Go โ€” Base64 encoding is ~12x faster than quote-aware escaping with regex detection.

Execution Overhead (Runtime Cost)

How much slower is the wrapped command vs raw execution? (10 iterations)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Command            โ”‚ Raw        โ”‚ v5 Python  โ”‚ Go base64  โ”‚ ฮ” Winner   โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ echo_simple        โ”‚   1.04 ms  โ”‚   0.95 ms  โ”‚   2.94 ms  โ”‚ v5 by 2ms  โ”‚
โ”‚ subst              โ”‚   1.75 ms  โ”‚   2.57 ms  โ”‚   4.54 ms  โ”‚ v5 by 2ms  โ”‚
โ”‚ loop               โ”‚   1.24 ms  โ”‚   1.20 ms  โ”‚   3.66 ms  โ”‚ v5 by 2.5msโ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Average Overhead   โ”‚    โ€”       โ”‚  +0.23 ms  โ”‚  +2.37 ms  โ”‚ v5 ๐Ÿ†      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Winner: v5 Python โ€” The Go hook's $(echo '...' | base64 -d) subshell adds ~2ms per command.

Character Overhead

How many extra characters does wrapping add?

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Command            โ”‚ Original โ”‚ v5 Python         โ”‚ Go base64         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ simple_echo        โ”‚    10    โ”‚ No wrap (0)       โ”‚ +38 chars         โ”‚
โ”‚ simple_ls          โ”‚    11    โ”‚ No wrap (0)       โ”‚ +37 chars         โ”‚
โ”‚ simple_date        โ”‚    14    โ”‚ No wrap (0)       โ”‚ +38 chars         โ”‚
โ”‚ subst_simple       โ”‚    16    โ”‚ +10 chars         โ”‚ +40 chars         โ”‚
โ”‚ subst_pipe         โ”‚    50    โ”‚ +10 chars         โ”‚ +50 chars         โ”‚
โ”‚ loop_pipe          โ”‚    40    โ”‚ +10 chars         โ”‚ +48 chars         โ”‚
โ”‚ multiline          โ”‚    30    โ”‚ +10 chars         โ”‚ +42 chars         โ”‚
โ”‚ complex            โ”‚    58    โ”‚ +10 chars         โ”‚ +54 chars         โ”‚
โ”‚ large (500 char)   โ”‚   505    โ”‚ No wrap (0)       โ”‚ +203 chars        โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ TOTAL              โ”‚          โ”‚ +50 chars ๐Ÿ†      โ”‚ +550 chars        โ”‚
โ”‚ Commands Wrapped   โ”‚          โ”‚ 5/9 (selective)   โ”‚ 9/9 (all)         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Winner: v5 Python โ€” Selective wrapping = 11x less character overhead.


๐ŸŽฏ Summary Table

โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘                         FINAL COMPARISON                                      โ•‘
โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
โ•‘                                                                               โ•‘
โ•‘   METRIC                    โ”‚ v5 Python          โ”‚ Go base64                  โ•‘
โ•‘  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€  โ•‘
โ•‘   Correctness               โ”‚ 66/66 (100%) ๐Ÿ†    โ”‚ 64/66 (97%)                โ•‘
โ•‘   Continuation repair       โ”‚ โœ… Yes             โ”‚ โŒ No                      โ•‘
โ•‘   Wrapping speed            โ”‚ 86.9 ยตs            โ”‚ 7.0 ยตs ๐Ÿ†                  โ•‘
โ•‘   Execution overhead        โ”‚ +0.2ms avg ๐Ÿ†      โ”‚ +2.4ms avg                 โ•‘
โ•‘   Character overhead        โ”‚ +50 chars ๐Ÿ†       โ”‚ +550 chars                 โ•‘
โ•‘   Selectivity               โ”‚ Smart (5/9)        โ”‚ Wrap all (9/9)             โ•‘
โ•‘   Code complexity           โ”‚ ~180 lines         โ”‚ ~75 lines ๐Ÿ†               โ•‘
โ•‘   Config file support       โ”‚ โŒ No              โ”‚ โœ… Yes                     โ•‘
โ•‘   Debug logging             โ”‚ โŒ No              โ”‚ โœ… Yes (w/ secret redact)  โ•‘
โ•‘   Escape markers            โ”‚ โŒ No              โ”‚ โœ… Yes (# no-wrap)         โ•‘
โ•‘                                                                               โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

๐Ÿ’ก Conclusions

When to use v5 Python:

  • You need 100% correctness including continuation repair
  • You care about execution speed (~2ms savings per command)
  • You want minimal overhead (selective wrapping)

When to use Go base64:

  • You prefer simpler, more elegant code
  • You value faster hook processing (12x faster wrapping)
  • You want config file support and debug logging
  • You don't encounter broken continuation patterns

Hybrid Opportunity

The ideal hook might combine both approaches:

  • Go's features: Config file, debug logging, escape markers
  • v5's correctness: Continuation repair for broken multi-line commands
  • v5's selectivity: Only wrap what needs wrapping

๐Ÿ”— References


Generated by comparing fix-bash-substitution.py v5 against claude-code-bash-tool-hook

smconner avatar Dec 10 '25 22:12 smconner

Nice work. Wish I could see the methodology for it, but this thread isn't the appropriate place probably.

Update: The performance comparisons above weren't apples-to-apples, so I ran some tests, you can see the results here: https://github.com/smconner/claude-code-bash-hook/pull/1#issuecomment-3647980701. I couldn't reproduce the failure case for the Go tool. It also covers robustness in the face of new problem pattern discovery as well as maintenance cost.

binaryphile avatar Dec 12 '25 19:12 binaryphile