[BUG] Shell snapshot captures compdef without completion state, breaking zsh functions that register completions

Resolved 💬 2 comments Opened May 11, 2026 by yaj-rb Closed Jun 10, 2026

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)

  1. Prepend to the snapshot preamble:

autoload -Uz compinit 2>/dev/null && compinit -C 2>/dev/null || true

  1. Cheap; re-initializes the completion system using the existing .zcompdump cache.
  2. Serialize the completion arrays when dumping the snapshot:

typeset -p _comps _services _patcomps _postpatcomps 2>/dev/null

  1. 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.

---

View original on GitHub ↗

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