Skip to content

gtr cd fzf picker on fish: set -l inside if/else scopes variables to the block, so cd never executes #169

Description

@nkpoid

Summary

On fish shell, gtr cd (with no arguments, using the fzf interactive picker) shows the picker correctly, but pressing Enter exits silently without changing directory. The same flow works on bash/zsh.

Environment

  • gtr version: v2.4.0
  • fish: 3.7.0 (also reproduced on newer)
  • fzf: installed and working

Root cause

In lib/commands/init.sh, inside _init_fish() (the gtr cd fzf path), _gtr_key and _gtr_line are declared with set -l inside an if/else block:

if test (count $_gtr_selection) -eq 1
    set -l _gtr_key ""
    set -l _gtr_line "$_gtr_selection[1]"
else
    set -l _gtr_key "$_gtr_selection[1]"
    set -l _gtr_line "$_gtr_selection[2]"
end

# ...
test -z "$_gtr_line"; and return 0

Unlike bash/zsh local, fish's set -l binds the variable to the innermost enclosing block (the if / else body), not the enclosing function. Once end is reached, _gtr_key and _gtr_line go out of scope and read as empty. The subsequent test -z "$_gtr_line"; and return 0 therefore always fires, so the function returns before __FUNC___run_post_cd_hooks "$dir" is ever called.

This is the same class of issue as fish-shell/fish-shell#4820. Ironically, the comment right above the if/else already acknowledges fish's quirky handling of empty command-substitution lines — but the fix below it reintroduces a different fish-specific scoping problem.

Reproduction (minimal)

#!/usr/bin/env fish
set _gtr_selection "/tmp/wt\tfeature-x"   # Enter pressed → count == 1

if test (count $_gtr_selection) -eq 1
    set -l _gtr_key ""
    set -l _gtr_line "$_gtr_selection[1]"
else
    set -l _gtr_key "$_gtr_selection[1]"
    set -l _gtr_line "$_gtr_selection[2]"
end

echo "key='$_gtr_key' line='$_gtr_line'"
# → key='' line=''         (both empty, scope already closed)

End-to-end repro with gtr itself:

  1. git gtr init fish | source (or via the cache file from the README snippet)
  2. In a repo with at least one worktree, run gtr cd
  3. Pick any entry, press Enter → fzf closes, no cd happens, shell stays in the original directory

Workaround: gtr cd <branch> works because it takes the non-fzf code path (git gtr go $argv[2..]), which doesn't depend on these variables.

Suggested fix

Declare the variables in the function scope before the if/else, and drop -l inside the branches so they assign to the outer bindings:

set -l _gtr_key
set -l _gtr_line
if test (count $_gtr_selection) -eq 1
    set _gtr_key ""
    set _gtr_line "$_gtr_selection[1]"
else
    set _gtr_key "$_gtr_selection[1]"
    set _gtr_line "$_gtr_selection[2]"
end

Happy to send a PR if the approach looks good. Worth also adding a basic fish smoke test — the current test suite is bats-only, so fish-side regressions (like this one) slip through.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions