[BUG] Shell snapshot captures interactive-only functions, eval-per-function pattern causes O(n) fork overhead (~8s per Bash tool call)

Resolved 💬 6 comments Opened Mar 6, 2026 by CervEdin Closed Apr 17, 2026

What happened?

Every Bash tool invocation takes ~8 seconds due to two compounding issues in the shell snapshot mechanism:

  1. Snapshot creation captures shell functions that a standard non-interactive shell would not have. bash -l -c 'declare -F | wc -l' returns 5 functions, but the snapshot contains 199. This suggests the snapshot process sources .bashrc in a way that bypasses the standard non-interactive guard (case $- in *i*) ...), picking up functions (mostly completions) that only belong in interactive shells.
  1. Each captured function is replayed via eval "$(echo '<base64>' | base64 -d)", which forks a process per function (~40ms each). With 199 functions this is 199 × 40ms ≈ 8s of pure fork overhead on every Bash tool call.

Shell snapshots (~/.claude/shell-snapshots/) grew from ~4KB / 4 eval statements (on 2.1.34) to 195KB / 199 eval statements (on 2.1.69). 194 of the 199 captured functions are shell completion functions (__git_*, __brew_*, __gh_*, __buf_*, __jira_*, __colima_*, __podman_*, etc.) that serve no purpose in the non-interactive Bash tool context.

What did you expect to happen?

Bash tool calls should execute near-instantly (as they did on 2.1.34 and earlier). The snapshot should not capture functions that aren't present in a standard non-interactive shell, and the replay mechanism should not have O(n) fork overhead per function.

Error messages / logs

Debug log (claude --debug) shows the timeline:

09:38:56.948 Creating shell snapshot for bash (/opt/homebrew/bin/bash)
09:38:58.534 Shell snapshot created successfully (194955 bytes)
09:38:58.535 Spawning shell without login (-l flag skipped)
09:39:06.537 FileHistory: Added snapshot  ← 8 seconds later

Timing confirmation:

$ time bash -c 'source ~/.claude/shell-snapshots/<new_snapshot>.sh'
real    7.8s

$ time bash -c 'source ~/.claude/shell-snapshots/<old_snapshot>.sh'
real    0.26s

199 trivial base64 evals (empty function bodies) also take ~8s, confirming the cost is fork overhead, not function content.

Steps to reproduce

  1. Have a .bashrc that sources completions (e.g., gcloud, brew bash_completion) outside of a non-interactive guard, or without one entirely — this is common in default/generated bashrc files
  2. Upgrade to Claude Code 2.1.69+
  3. Run any Bash tool command (e.g., !echo hi)
  4. Observe ~8 second delay

The key condition is having functions defined in .bashrc outside of (or before) a non-interactive guard like case $- in *i*) ;; *) return;; esac — or having no such guard at all, which is common in default/generated bashrc files. Completion functions are the most common case since there are many of them, but any functions sourced there would be affected.

Is this a regression?

Yes

Last working version

2.1.34

Claude Code version

2.1.69 (also reproduces on 2.1.70)

Platform

Anthropic (Claude Max)

Operating system

macOS Darwin 25.3.0 (Apple Silicon)

Terminal / shell

bash 5.x (/opt/homebrew/bin/bash)

Additional information

Workaround: Ensure .bashrc has a non-interactive guard, and move any heavy function/completion sourcing below it:

# If not running interactively, don't do anything
case $- in
    *i*) ;;
    *) return;;
esac

# Source completions and other interactive-only functions AFTER the guard
source /path/to/completions.sh
source /path/to/bash_completion.sh

Suggested fixes:

  • Investigate why snapshot creation captures functions not present in bash -l -c (non-interactive login shell)
  • Consider inlining decoded function bodies directly in the snapshot instead of the eval "$(echo '...' | base64 -d)" pipe pattern, eliminating the per-function fork overhead entirely. Or just do one eval.

Related issues: #25016, #19585, #10181 — similar symptoms, no root cause identified

View original on GitHub ↗

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