Skip to content

fix: make bash completion work without the bash-completion package#355

Open
gtsiolis wants to merge 1 commit into
mainfrom
devx-950-bash-completion-broken-on-stock-macos-_get_comp_words_by_ref
Open

fix: make bash completion work without the bash-completion package#355
gtsiolis wants to merge 1 commit into
mainfrom
devx-950-bash-completion-broken-on-stock-macos-_get_comp_words_by_ref

Conversation

@gtsiolis

@gtsiolis gtsiolis commented Jul 2, 2026

Copy link
Copy Markdown
Member

Fixes DEVX-950 (reported by DevRel).

Problem

On stock macOS (bash 3.2.57, no bash-completion package) every Tab press failed with

_get_comp_words_by_ref: command not found

and produced zero completions. Cobra's generated bash script depends on that function from the bash-completion package on both of its init paths — the "Mac fallback" it embeds assumes Homebrew's legacy bash-completion v1.3 is installed, and a truly stock Mac has no bash-completion at all. (Upstream treats this as a documented dependency, see spf13/cobra#2213; kubectl/gh/docker all answer with "install bash-completion" docs.) Zsh was unaffected because Cobra's zsh script is self-contained.

Fix

  • Prepend a guarded pure-bash _get_comp_words_by_ref fallback to the generated script (cmd/completion.go), defined only when the bash-completion package is absent — the approach git ships in git-completion.bash, which is why git completion works on stock Macs. Unlike git's fallback, it uses COMP_LINE to recover word adjacency, matching the real package's reassembly semantics for shapes like --config=x vs --config= x. The body is bash 3.2 compatible.
  • Generate both the fallback and Cobra's script body against the writer resolved at execution time (delegating to Cobra's captured RunE would split the two halves across writers when SetOut is called after NewRootCmd).
  • Replace the bash help text: the default recommended source <(lstk completion bash), which is a silent no-op on bash 3.2 (sourcing a process substitution reads nothing there); recommend eval "$(lstk completion bash)" instead, which works on every bash.

When the bash-completion package is installed, the guard skips the fallback and behavior is unchanged. Zsh/fish/powershell output is byte-identical to before.

Testing

  • 6 integration tests drive the real generated script in a bare bash (--noprofile --norc, minimal env — exactly the stock-macOS condition, reproducible on any CI OS): completion works without the package, whitespace-vs-adjacent wordbreak reassembly parity (4 fixtures), and the fallback yielding to an installed package. All failed before the fix with the exact reported error.
  • 2 unit tests pin the single-writer wiring and --no-descriptions passthrough.
  • Verified end-to-end on stock /bin/bash 3.2.57 and Homebrew bash 5.2 in a clean env: eval "$(lstk completion bash)" + Tab completes lstk ststart status stop, including --flag=value and pod: word shapes.

Cobra's generated bash completion script calls _get_comp_words_by_ref from
the bash-completion package on both of its init paths, so on stock macOS
(bash 3.2, no bash-completion) every Tab press failed with
"_get_comp_words_by_ref: command not found" (DEVX-950).

Prepend a guarded pure-bash fallback to the generated script (defined only
when the package is absent, the git-completion.bash approach). The fallback
re-joins COMP_WORDS pieces split at COMP_WORDBREAKS separators using
COMP_LINE to recover adjacency, matching the package's reassembly semantics,
and stays bash 3.2 compatible.

Both the fallback and Cobra's script body are generated against the writer
resolved at execution time; delegating to Cobra's own RunE would write the
body to the writer captured at InitDefaultCompletionCmd time, splitting the
two halves across destinations when SetOut is called after NewRootCmd.

Also replace the bash completion help text: the default recommended
'source <(lstk completion bash)', which is a silent no-op on bash 3.2 —
recommend 'eval "$(lstk completion bash)"' instead.
@gtsiolis gtsiolis requested a review from a team as a code owner July 2, 2026 09:37
@gtsiolis gtsiolis self-assigned this Jul 2, 2026
@gtsiolis gtsiolis added semver: patch docs: skip Pull request does not require documentation changes labels Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs: skip Pull request does not require documentation changes semver: patch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant