[BUG] Shell snapshot leaking definitions into stdout

Open 💬 0 comments Opened Jul 9, 2026 by asmrtfm

Summary

The shell snapshot mechanism has two bugs: it leaks function definitions into stdout during capture, and it captures non-exported system functions.

Confirmed on all versions from 2.1.177 to 2.1.205

Bug 1: declare -fx output leaks into stdout

The harness runs declare -fx (without arguments) during snapshot capture. This prints all exported function definitions to stdout, alphabetically sorted. The output leaks into tool results during normal interactive chat sessions and into $() subshells, breaking commands like claude -p.

declare -fx with vs. without arguments:

# With a name — silent, no stdout:
$ bash -c 'f() { :; }; output=$(declare -fx f); echo "[$output]"'
[]

# Without a name — dumps all exported functions to stdout:
$ bash -c 'f() { :; }; export -f f; output=$(declare -fx); echo "[$output]"'
[f () { :; }
declare -fx f]

This is not limited to claude -p. The leaked output appears in normal chat sessions in tool results.

Bug 2: Snapshot captures non-exported system functions

The snapshot file (~/.claude/shell-snapshots/snapshot-bash-*.sh) contains base64-encoded eval blocks for each function. Decoding the full snapshot reveals 13 functions — 7 from user rc files and 6 from /etc/profile.d/gawk.sh:

cd () 
{ ... }

cdw () 
{ ... }

climb () 
{ ... }

gawklibpath_append () 
{ 
    [ -z "$AWKLIBPATH" ] && AWKLIBPATH=`gawk 'BEGIN {print ENVIRON["AWKLIBPATH"]}'`;
    export AWKLIBPATH="$AWKLIBPATH:$*"
}

gawklibpath_default () 
{ 
    unset AWKLIBPATH;
    export AWKLIBPATH=`gawk 'BEGIN {print ENVIRON["AWKLIBPATH"]}'`
}

gawklibpath_prepend () 
{ 
    [ -z "$AWKLIBPATH" ] && AWKLIBPATH=`gawk 'BEGIN {print ENVIRON["AWKLIBPATH"]}'`;
    export AWKLIBPATH="$*:$AWKLIBPATH"
}

gawkpath_append () 
{ 
    [ -z "$AWKPATH" ] && AWKPATH=`gawk 'BEGIN {print ENVIRON["AWKPATH"]}'`;
    export AWKPATH="$AWKPATH:$*"
}

gawkpath_default () 
{ 
    unset AWKPATH;
    export AWKPATH=`gawk 'BEGIN {print ENVIRON["AWKPATH"]}'`
}

gawkpath_prepend () 
{ 
    [ -z "$AWKPATH" ] && AWKPATH=`gawk 'BEGIN {print ENVIRON["AWKPATH"]}'`;
    export AWKPATH="$*:$AWKPATH"
}

lsdb () 
{ ... }

lsn () 
{ ... }

lspg () 
{ ... }

sourcerer () 
{ ... }

The 6 gawk functions are byte-for-byte identical to /etc/profile.d/gawk.sh shipped by the gawk Debian/Ubuntu package. They are defined but never exported in a login shell:

$ bash -l -c 'declare -F' | grep gawk    # defined: yes (6 functions)
$ bash -l -c 'declare -Fx' | grep gawk   # exported: (none)
$ grep 'export -f' /etc/profile.d/gawk.sh  # source never exports: (none)

The harness promotes non-exported system functions to exported. It also drops actually-exported user functions — for example, _glow appears in the Bug 1 declare -fx stdout leak (confirming it is exported) but is missing from the snapshot file above.

Environment

  • Ubuntu 24.04, Bash 5.2
  • Claude Code CLI 2.1.177 through current latest

Steps to Reproduce

  1. Have exported functions in your shell (.bash_aliases, .bashrc, etc.)
  2. Start a Claude Code session
  3. Observe declare -fx output appearing in tool results during normal chat
  4. Or run claude -p "$(echo hello)" and observe the function dump replacing the prompt

Expected Behavior

  • Snapshot capture should not leak any output into stdout
  • Only actually-exported functions should be captured

View original on GitHub ↗