Skip to content

fix: fish completion breaks under subshell evaluation - #4025

Open
kokhlo wants to merge 3 commits into
sharkdp:masterfrom
kokhlo:fix/fish-completion-subshell-3909
Open

kokhlo wants to merge 3 commits into
sharkdp:masterfrom
kokhlo:fix/fish-completion-subshell-3909

Conversation

@kokhlo

@kokhlo kokhlo commented Sep 29, 2026

Copy link
Copy Markdown

fish sources a completion file in the scope of whatever triggers the first completion. With the default binding that is the interactive session itself, so an unscoped set in bat.fish.in happens to end up global and everything works. Bind <TAB> to a function that calls commandline --function complete — which is what fish-autopair's _autopair_tab does — and the file is sourced inside that function instead, so set bat bat becomes a local of _autopair_tab and disappears the moment it returns. The helper functions are called later, from a different scope, and see an empty $bat:

/usr/share/fish/vendor_completions.d/bat.fish: The expanded command was empty.
command $bat --no-paging --list-languages

With that, bat -l followed by <TAB> produces no language list at all. Reproduced on fish 4.9.3 by sourcing the rendered file inside a function and then invoking the helper from outside it: zero candidates, with exactly the error above.

set -g makes the scope explicit instead of leaving it to chance. The reason this is not an export problem is worth spelling out, because it looks like one: command substitution in fish is a fork, and a fork inherits non-exported globals. So set -gx also silences the error, but it does it by putting bat=bat into the environment of every process the shell starts, where nothing needs it and a program that happens to read $bat would pick up a variable it was never meant to see. Measured, the export leaks while the global does not:

$ fish -c 'set -g bat bat;  env | grep -c "^bat="'   # 0
$ fish -c 'set -gx bat bat; env | grep -c "^bat="'   # 1

Checking the fix against both renderings, since the executable name is substituted at build time and Debian/Ubuntu gets batcat:

                        before   after
  --language, in function    0       8
  --language, top level      8       8
  --theme, both cases        8       8

--theme is unaffected either way — its list is built by a command substitution in the complete -a argument rather than by a helper function, so it is still evaluated where it can see the variable. Top-level behaviour is unchanged at 8, so this is a fix to the broken path rather than a change to the working one. The other three helpers in the file return cleanly from a foreign scope after the change, and fish --no-execute parses the rendered file for both bat and batcat.

There are two earlier attempts at this that appear to have gone quiet — #3910 and #4006 — so if either is still worth reviving, happy to close this in favour of that instead.

Closes #3909

fish sources a completion file in the scope of whatever triggers the first
completion. With the default binding that is the interactive session, so an
unscoped 'set' ends up global by accident. Bind <TAB> to a function that
calls 'commandline --function complete' -- as fish-autopair's _autopair_tab
does -- and the file is sourced inside that function, making $bat local to
it. The completion helpers run later in a different scope, so $bat expands
empty and 'bat -l' <TAB> fails with 'The expanded command was empty'.

'set -g' makes the scope explicit. Command substitution in fish is a fork
and inherits non-exported globals, so the helpers now resolve $bat without
exporting it into the environment of every child process.

Verified on fish 4.9.3 for both the bat and batcat renderings: the
--language candidates go from 0 to 8 when the file is sourced inside a
function, stay at 8 when sourced at top level, and --theme is unaffected
either way.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[fish] Completion breaks under subshell completion evaluation: "command $bat --list-languages": command is empty

1 participant