[BUG] Shell snapshot captures compdef without completion state, breaking zsh functions that register completions
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?
Shell snapshot captures compdef without completion state, breaking zsh functions that register completions
What Should Happen?
Shell snapshot captures compdef with completion state so that zsh function are able to register completions
Error Messages/Logs
Steps to Reproduce
compdef:153: _comps: assignment to invalid subscript range
This breaks any dotfile that wraps a command in a function and registers a completion for it — it is not specific to one dotfile or one company's setup.
Minimal repro
Add this to ~/.zshrc (or any file sourced at interactive startup):
autoload -Uz compinit && compinit
foo() {
if [[ -z $_FOO_COMPDEF_DONE ]]; then
compdef _git foo # any registered completion function works
_FOO_COMPDEF_DONE=1
fi
command git "$@"
}
Launch Claude Code from a terminal where that file was sourced, then run foo --version through the Bash tool.
Expected: git version output.
Actual:
compdef:153: _comps: assignment to invalid subscript range
Outside Claude, foo --version in a fresh interactive zsh works.
Claude Model
Sonnet (default)
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.139
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
Title: Shell snapshot captures compdef without completion state, breaking zsh functions that register completions
Environment
- Claude Code: 2.1.139 (macOS arm64 binary)
- Shell: zsh 5.9
- OS: macOS (Darwin 25.4.0)
Summary
The zsh shell snapshot written to ~/.claude/shell-snapshots/snapshot-zsh-<ts>-<rand>.sh dumps the full body of compdef as an inline function, but does not preserve the associative arrays it mutates (_comps, _services, _patcomps,
_postpatcomps). Those arrays are normally declared by compinit at shell startup. Because the snapshot is replayed in a non-interactive zsh -c, compinit is never run inside the Bash tool subshell, so any captured function that calls compdef
fails with:
compdef:153: _comps: assignment to invalid subscript range
This breaks any dotfile that wraps a command in a function and registers a completion for it — it is not specific to one dotfile or one company's setup.
Minimal repro
Add this to ~/.zshrc (or any file sourced at interactive startup):
autoload -Uz compinit && compinit
foo() {
if [[ -z $_FOO_COMPDEF_DONE ]]; then
compdef _git foo # any registered completion function works
_FOO_COMPDEF_DONE=1
fi
command git "$@"
}
Launch Claude Code from a terminal where that file was sourced, then run foo --version through the Bash tool.
Expected: git version output.
Actual:
compdef:153: _comps: assignment to invalid subscript range
Outside Claude, foo --version in a fresh interactive zsh works.
Root cause
The snapshot generator serializes functions (typeset -f / functions) and environment, but does not serialize the completion-system state populated by compinit. The replay preamble does not run compinit either. Inspecting a snapshot:
$ grep -n '^compdef ()\|typeset._comps\|autoload.compinit' ~/.claude/shell-snapshots/snapshot-zsh-*.sh
90:compdef () {
# no typeset -A _comps, no compinit
Inside the replayed shell:
$ typeset -p _comps
(eval):typeset:2: no such variable: _comps
So compdef's _comps[$cmd]="$func" hits an undeclared name and zsh reports the subscript error.
Proposed fixes (either is sufficient)
- Prepend to the snapshot preamble:
autoload -Uz compinit 2>/dev/null && compinit -C 2>/dev/null || true
- Cheap; re-initializes the completion system using the existing .zcompdump cache.
- Serialize the completion arrays when dumping the snapshot:
typeset -p _comps _services _patcomps _postpatcomps 2>/dev/null
- Preserves exact state without re-running compinit.
Option 1 is simpler and also fixes any other completion-dependent state the user hasn't registered yet.
Current workaround
In each affected dotfile, guard the compdef call:
if (( ${+_comps} )); then
compdef _foo foo
fi
This is fine for a single dotfile owner, but every team hitting this has to patch their own scripts independently, and it silently disables completion registration in the subshell rather than fixing the underlying environment gap.
Why this matters
Zsh completion-registering functions are very common (homebrew, rustup, mise, pyenv, fnm, nvm, gcloud, kubectl, git helpers, etc.). Any wrapper function that lazy-registers its completion hits this the first time it runs through the Bash
tool. For users, the failure mode looks like "git is broken inside Claude Code," which is a confusing UX.
---
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗