[BUG] Shell snapshot leaking definitions into stdout
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
- Have exported functions in your shell (
.bash_aliases,.bashrc, etc.) - Start a Claude Code session
- Observe
declare -fxoutput appearing in tool results during normal chat - 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