#14125·ohmyzsh

Background completion regeneration (#14000) triggers zcompdump rebuilds when shells start concurrently

Author: krax1337Created Sep 24, 2026Updated Sep 24, 2026
LabelsArea: coreTopic: completion

Describe the bug

Since #14000 (fix for #13964), plugins that regenerate their completion file in the background write the temporary file into $ZSH_CACHE_DIR/completions/:

bash
() {
  local TMPPREFIX="$ZSH_CACHE_DIR/completions/_kubectl"
  zf_mv -f -- =( kubectl completion zsh 2> /dev/null ) "$TMPPREFIX"
} &|

=( ... ) creates a file named $TMPPREFIX + random suffix (e.g. _kubectlH97hAb), and that directory is in $fpath. compinit counts every _* file in $fpath and compares it against the #files: header of the zcompdump. If a shell starts while another shell's background job has a temp file there, the count is off by one and the whole dump is rebuilt. The next shell then sees the original count again and rebuilds it once more.

Result: new shells are intermittently ~3x slower, depending only on timing. It is easy to hit when opening several tabs/panes in a row, restoring a multiplexer session, or when tools spawn zsh -i -c in the background. Shells that get killed during the rebuild also leave behind partial ~/.zcompdump-*.$HOST.$PID files (from compdump), and I had a stale ~/.zcompdump-*.lock directory left over as well.

This affects all 47 plugins touched by #14000 (docker, kubectl, helm, gh, uv, bun, ...).

Steps to reproduce

Deterministic reproduction (simulates the temp file):

  1. Enable e.g. the docker plugin and start a shell once so the dump exists.
  2. Run:
    bash
    D=$ZSH_COMPDUMP   # e.g. ~/.zcompdump-<host>-5.9
    grep -m1 '^#files' $D                             # #files: 973
    touch $ZSH_CACHE_DIR/completions/_dockerTEST
    time zsh -i -c exit                               # slow, dump rebuilt
    grep -m1 '^#files' $D                             # #files: 974
    rm $ZSH_CACHE_DIR/completions/_dockerTEST
    time zsh -i -c exit                               # slow again, dump rebuilt back to 973

Real-world reproduction: start one interactive shell, then start a second one ~100-150 ms later, and check whether the zcompdump mtime changed. My measurements (docker, kubectl, helm plugins enabled; 3 runs per delay):

Second shell started after the first Startup time Dump rebuilt
alone 0.21-0.23 s no
0-50 ms 0.21-0.69 s occasionally
100-150 ms 0.57-0.69 s every time
200-300 ms 0.22-0.24 s no

Expected behavior

Background completion regeneration should not change the set of files compinit sees, so the zcompdump is only rebuilt when completions actually change.

A possible fix that keeps the atomic rename from #14000: put the temp file outside the $fpath directory but on the same filesystem, e.g.

bash
local TMPPREFIX="$ZSH_CACHE_DIR/_kubectl"

zf_mv still renames within $ZSH_CACHE_DIR, so the file still appears atomically, and compinit never sees the temporary file. Happy to open a PR with this change across the affected plugins if that direction works for you.

OS / Linux distribution

macOS 26.5 (Darwin 25.5.0, arm64)

Zsh version

5.9

Oh My Zsh version

master (6421f8e); the relevant code is unchanged on current master

Terminal emulator

Ghostty 1.3.1 (with the herdr multiplexer)

Additional context

Plugins: aliases alias-finder ssh-agent fasd git docker-compose kubectl ansible command-not-found fzf gcloud gitfast helm docker pip tmux golang zsh-syntax-highlighting zsh-autosuggestions zsh-completions kube-ps1

Related: #14000, #13964. cc @carlosala @mcornella