Skip to content

fix: replace compinit re-run with autoload+compdef for site-functions completions#359

Merged
paolomainardi merged 1 commit intomasterfrom
fix/compinit-bashcompinit-conflict
Mar 6, 2026
Merged

fix: replace compinit re-run with autoload+compdef for site-functions completions#359
paolomainardi merged 1 commit intomasterfrom
fix/compinit-bashcompinit-conflict

Conversation

@paolomainardi
Copy link
Member

@paolomainardi paolomainardi commented Mar 6, 2026

User description

Summary

Problem

Commits #355 and #356 together created a blind spot:

Scenario init.zsh adds dir? fpath changed? compinit re-runs? Completions work?
dir not in fpath yet yes yes yes yes
dir in fpath, compinit ran with it no no no yes
dir in fpath, compinit ran without it no no no no

The third case happens when .zshrc adds site-functions to fpath after compinit (e.g. oh-my-zsh) but before sourcing sparkdock.

Solution

Instead of trying to detect when compinit needs re-running (which is inherently fragile and conflicts with bashcompinit), register completions directly:

autoload -Uz "${_f:t}"           # lazy — no file I/O
compdef "${_f:t}" "${${_f:t}#_}" # hash table write

This is generic (no tool-specific knowledge), idempotent, and never interferes with bashcompinit.

Testing

Tested locally — ajust <TAB>, aws <TAB>, and gcloud <TAB> all work correctly regardless of fpath/compinit ordering in .zshrc.


PR Type

Bug fix, Enhancement


Description

  • Replace fragile fpath-snapshot + conditional compinit with direct autoload+compdef registration

  • Fix completions not discovered when user's compinit runs before sparkdock is sourced

  • Avoid re-running compinit, which previously wiped bashcompinit registrations (aws, gcloud)

  • Only initialize completion system if compdef is not already available


Diagram Walkthrough

flowchart LR
  A["User .zshrc compinit"] -- "may run before sparkdock" --> B["sparkdock.zshrc"]
  B -- "sources" --> C["init.zsh"]
  B -- "checks compdef missing" --> D["autoload compinit + compinit"]
  B -- "iterates _* files" --> E["~/.local/share/zsh/site-functions"]
  E -- "autoload -Uz + compdef" --> F["Completions registered\n(sjust, ajust, opencode...)"]
  D -- "skipped if compdef exists" --> F
  F -- "no compinit re-run" --> G["bashcompinit intact\n(aws, gcloud)"]
Loading

File Walkthrough

Relevant files
Bug fix
sparkdock.zshrc
Replace fpath-diff compinit with direct autoload+compdef registration

config/shell/sparkdock.zshrc

  • Removed fpath-snapshot mechanism (_sparkdock_fpath_before) and
    fpath-diff conditional around compinit
  • Added guard to only run compinit if compdef is not already defined
    (i.e., completion system never initialized)
  • Added loop over ~/.local/share/zsh/site-functions/_* files to register
    completions directly via autoload -Uz and compdef, bypassing need to
    re-run compinit
  • Prevents wiping of bashcompinit registrations (aws, gcloud) caused by
    re-running compinit
+16/-9   

… completions

The fpath-diff approach from #355/#356 had a blind spot: when the user
added site-functions to fpath after their own compinit but before
sparkdock, neither the duplicate guard (init.zsh) nor the fpath-change
detection (sparkdock.zshrc) would trigger compinit, leaving completions
like ajust, ljust unloaded.

Re-running compinit is also problematic because it wipes bash-style
completions (aws, gcloud) registered earlier via bashcompinit.

Replace the entire fpath-snapshot + conditional-compinit mechanism with
direct autoload + compdef registration of completion files in
site-functions. autoload is lazy (no file I/O) and compdef writes to a
hash table — both are effectively free. This works regardless of
fpath/compinit ordering and never interferes with bashcompinit.
Copilot AI review requested due to automatic review settings March 6, 2026 17:01
@paolomainardi paolomainardi merged commit 1113a47 into master Mar 6, 2026
6 checks passed
@sparkfabrik-ai-bot
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

356 - Partially compliant

Compliant requirements:

  • Avoids re-running compinit entirely, preventing wipe of bashcompinit registrations
  • The fpath-snapshot comparison mechanism is removed, so no false fpath change detection occurs

Non-compliant requirements:

  • The duplicate fpath entry guard in init.zsh (using (Ie) subscript flag) is not shown in this diff — unclear if it was addressed or left as-is

Requires further human verification:

  • Verify that init.zsh still correctly avoids duplicate fpath entries (not visible in this diff)

355 - Partially compliant

Compliant requirements:

  • Completions from site-functions are now registered directly via autoload + compdef, working regardless of compinit ordering

Non-compliant requirements:

Requires further human verification:

  • Confirm CHANGELOG.md was updated appropriately for this new approach
  • Test that completions (ajust, ljust, opencode, etc.) are discovered in all ordering scenarios
⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

compdef availability

The new code calls compdef inside the for loop unconditionally, but compdef is only guaranteed to exist if compinit has been run. The guard above only runs compinit if compdef is not yet available via whence. However, if compinit was never run at all (neither by the user nor by sparkdock), compdef won't exist and the loop will silently fail or error. The 2>/dev/null suppresses errors, which may hide real problems.

for _f in "${_sparkdock_site_dir}"/_*(N); do
  autoload -Uz "${_f:t}"
  compdef "${_f:t}" "${${_f:t}#_}" 2>/dev/null
done
Glob pattern scope

The glob _*(N) matches all files starting with _ in site-functions, including subdirectories. There is no -f (file-only) filter, so directories named _something would also be processed, potentially causing autoload and compdef to behave unexpectedly.

for _f in "${_sparkdock_site_dir}"/_*(N); do
  autoload -Uz "${_f:t}"
  compdef "${_f:t}" "${${_f:t}#_}" 2>/dev/null
done

@sparkfabrik-ai-bot
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Ensure autoload can locate site-functions files

The autoload call uses the bare function name (e.g., _git) but zsh needs to know
where to find it. Since _sparkdock_site_dir may not be in fpath, the autoload will
silently fail to locate the file. You should either add the directory to fpath
first, or use the full path form autoload -Uz "${_f}" which loads from the explicit
path.

config/shell/sparkdock.zshrc [32-33]

+fpath=("${_sparkdock_site_dir}" "${fpath[@]}")
 autoload -Uz "${_f:t}"
 compdef "${_f:t}" "${${_f:t}#_}" 2>/dev/null
Suggestion importance[1-10]: 7

__

Why: This is a valid concern - autoload with just the function name (e.g., _git) requires the directory to be in fpath to locate the file. If _sparkdock_site_dir isn't in fpath, the autoload will silently fail. Adding the directory to fpath before the loop (rather than inside it) would be more efficient and correct.

Medium
General
Validate derived command name before registering completion

The compdef call strips only the leading underscore to derive the command name
(e.g., _gitgit), but many completion files use naming conventions like git* or
are meant to complete multiple commands. Additionally, compdef requires the
completion system to already be initialized; if compdef is not available, this
silently fails. The 2>/dev/null suppresses errors that could indicate real problems.
Consider at minimum checking that the derived command name is non-empty and
meaningful before calling compdef.

config/shell/sparkdock.zshrc [33]

-compdef "${_f:t}" "${${_f:t}#_}" 2>/dev/null
+local _cmd="${${_f:t}#_}"
+if [[ -n "${_cmd}" ]]; then
+  compdef "${_f:t}" "${_cmd}" 2>/dev/null
+fi
Suggestion importance[1-10]: 3

__

Why: The check for a non-empty _cmd is marginally useful since stripping _ from a filename starting with _ will always yield a non-empty string (the glob _*(N) ensures files start with _). The suggestion about multi-command completions is valid but the proposed fix doesn't address that concern.

Low

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the fragile fpath-snapshot + conditional compinit re-run mechanism (introduced in #355/#356) with direct autoload -Uz + compdef registration of completion files from ~/.local/share/zsh/site-functions. The fix addresses a remaining blind spot where completions weren't discovered when a user's .zshrc added site-functions to fpath after calling compinit but before sourcing sparkdock.

Changes:

  • Removed the fpath-before/after snapshot comparison and the unset _sparkdock_fpath_before cleanup.
  • Replaced conditional compinit re-run with a whence -w compdef guard that only initializes the completion system if it has never been set up.
  • Added a loop that iterates over all _* files in ~/.local/share/zsh/site-functions and registers each via autoload -Uz + compdef, independent of fpath/compinit ordering.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +25 to +37
# Register site-functions completions directly via autoload + compdef.
# This avoids re-running compinit (which wipes bashcompinit registrations
# like aws, gcloud) and works regardless of whether the user added
# site-functions to fpath before or after their own compinit call.
_sparkdock_site_dir="${HOME}/.local/share/zsh/site-functions"
if [[ -d "${_sparkdock_site_dir}" ]]; then
for _f in "${_sparkdock_site_dir}"/_*(N); do
autoload -Uz "${_f:t}"
compdef "${_f:t}" "${${_f:t}#_}" 2>/dev/null
done
unset _f
fi
unset _sparkdock_site_dir
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR has no CHANGELOG entry documenting the approach change (replacing the fpath-snapshot + conditional compinit mechanism with direct autoload+compdef registration). Per codebase conventions (see CHANGELOG.md), all code changes must be documented with a one-line entry in CHANGELOG.md. The existing entry at line 41 describes the problem fixed in #355, but this PR introduces a new fix strategy that supersedes that approach and should be recorded as a fix entry.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants