fix(shell): resolve binary in fish package completions to avoid wrapper recursion - #3241
Conversation
…er recursion `wt config shell completions fish` (used by package managers such as Homebrew for the vendored `vendor_completions.d/wt.fish`) emitted clap's raw registration, which calls the bare `wt` command at TAB time (`COMPLETE=fish wt -- …`). When worktrunk's lazy-load wrapper function shadows the binary, that bare call re-enters the wrapper. fish has already exported `COMPLETE=fish`, so the wrapper's `command wt config shell init fish | source` emits a completion registration instead of the init script — the real `wt` function is never defined — and the wrapper's trailing `wt $argv` recurses into the stub until fish's call-stack limit aborts it. Emit the same binary-resolving registration as `wt config shell install` (via `type -P`, with the `WORKTRUNK_BIN` override) so the completion bypasses the wrapper. Mirrors the existing make_zsh_autoload_safe transform. Closes #3240 Co-Authored-By: Claude <noreply@anthropic.com>
|
can you break down the sequence more? why does this only apply before having run because running |
worktrunk-bot
left a comment
There was a problem hiding this comment.
The fix itself reads correctly: routing wt config shell completions fish through fish_completion_content makes the package-manager / Homebrew-vendored registration resolve the real binary via type -P (with WORKTRUNK_BIN override), the same form wt config shell install already wrote, so the bare-wt registration that recurses through the lazy-load wrapper is gone. It parallels the existing make_zsh_autoload_safe transform, the dropped EnvFish import is genuinely unused, and the regression assertions in test_completions_for_all_shells pin the type -P / WORKTRUNK_BIN form and reject the bare COMPLETE=fish wt call. Bash/zsh don't share the bug because their wrapper has an explicit COMPLETE guard that calls command "${WORKTRUNK_BIN:-wt}".
Flagging for human sign-off per worktrunk's data-loss-surface policy: this PR edits src/commands/configure_shell.rs, which already contains fs::remove_file (the shell-config uninstall/install paths). The diff there is a one-token visibility change (fn fish_completion_content → pub(crate) fn) at a location far from the removal logic, so it can't alter deletion behavior — but the policy holds a change touching that file for maintainer review rather than letting an agent judge it harmless. Requesting @max-sixty.
Not approving — self-authored PR (GitHub blocks self-approval), and the data-safety surface is the maintainer's call regardless.
|
Good question — the "before having run The two
Why running
Why tab-completing in that same fresh session recurses ( The old vendored completion registered
So the precise condition isn't "before having run The fix sidesteps the whole chain: the package-manager completion now resolves |
|
Heads-up for review: The failing test is All other required checks, including |
|
@worktrunk-bot rerun windows test and create a new PR to fix the flake pls |
|
Done — fix PR and reruns: Fix PR: #3242 de-flakes Reruns: the first windows rerun cleared Why de-flake the test rather than chase the exit codeThe failing run finished in 2.74s — under the test's 5s exit wait — so the child exited on its own (not killed by the timeout). The switch pipeline ran, emitted its JSON payload, and printed the success message; the non-zero exit comes from somewhere after that success output, on Windows only. I couldn't reproduce it on the Linux CI runner to root-cause it, so #3242 de-flakes the cursor-stickiness test (which was never meant to assert process-exit hygiene) rather than shipping an unverifiable product change. The repeated, differing Windows symptoms across reruns point to a broader class of picker-PTY flakiness worth a separate look. |
…n feature
`test_switch_picker_worktree_row_comments_tab_shows_thread` drove the
picker with a single blind `Down` (`("\x1b[B", None)`) to move the cursor
onto `feature`, then Alt-1 and waited for `feature`'s `diff --git`. On
Windows CI it timed out after 30s — the captured screen showed the `>`
pointer stranded on `main` ("○ main has no uncommitted changes" in the
HEAD± preview), so the awaited `diff --git` never appeared.
Root cause: `feature`'s row decorates asynchronously (primed CI status
resolves to "has PR" in the background). When that resolution lands it
refreshes skim's item list, which resets the cursor to the top — undoing
the `Down` and leaving the pointer on the primary worktree. The existing
preview-reissue nudge then re-ran Alt-1 against `main` forever.
Make `Down` cursor-confirmed: when a Up/Down arrow step carries expected
content, treat it as the target row name and re-issue the (idempotent)
arrow via `wait_for_stable_until`'s nudge until the `>` pointer lands on
that row and the screen settles — by which point the list has stopped
refreshing. The comments-tab step becomes `("\x1b[B", Some("feature"))`.
This mirrors the alt-digit preview re-issue added in #3238 and reuses
`wait_for_cursor_on_row`'s pointer predicate (factored out as
`cursor_points_at`). Arrow steps that pass `None` (every other test) are
unaffected — they still fall through to a plain stability wait.
Refs #3241
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rsion (#3250) Follow-up to #3241, requested in #3240. #3241 made the completion files **wt itself writes** resolve the binary, so they no longer re-enter the fish lazy-load stub. But the stub (`functions/wt.fish`) is still unguarded, leaving one narrow residual case: a **stale third-party completion** that calls the bare `wt` command with `COMPLETE` set — e.g. an old Homebrew `vendor_completions.d/wt.fish` left on disk from before an upgrade — re-enters the stub and recurses to fish's call-stack limit until the package is rebuilt. This adds the same `COMPLETE` short-circuit that bash and zsh already use, so the stub delegates straight to the binary in completion mode regardless of which completion file is active: ```fish function wt if set -q COMPLETE command wt $argv # bypass the stub, let the binary emit completions return end command wt config shell init fish | source ... end ``` This makes fish immune to stale third-party completions, matching bash/zsh. ## Changes - `templates/fish_wrapper.fish` — add the `COMPLETE` guard to the lazy-load stub. - `src/shell/mod.rs` — `test_fish_wrapper_guards_completion_mode` regression test (#3240). - Regenerated the `configure_shell_fish_dry_run` snapshot. Closes #3240. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: worktrunk-bot <worktrunk-bot@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Problem
On a fresh fish shell, tab-completing
wt <TAB>before running anywtcommand recurses into the lazy-loadwtwrapper function until fish aborts with "The call stack limit has been exceeded" (#3240).Root cause:
wt config shell completions fish— the script package managers ship as the vendoredshare/fish/vendor_completions.d/wt.fish(e.g. Homebrew'sgenerate_completions_from_executable) — emitted clap's raw registration, which calls the barewtcommand at TAB time:When worktrunk's lazy-load wrapper function shadows the binary (the state before the first
wtinvocation), that barewtre-enters the wrapper. fish has already exportedCOMPLETE=fish, so the wrapper'scommand wt config shell init fish | sourcemakes the binary emit a completion registration instead of the init script — the realwtfunction is never defined — and the wrapper's trailingwt $argvrecurses into the stub to the call-stack limit.The install-path completion (
wt config shell install) was already safe: it resolves the binary viatype -P wt, bypassing the wrapper. Only the package-manager path was exposed.Solution
Make
wt config shell completions fishemit the same binary-resolving registration aswt config shell install, reusingconfigure_shell::fish_completion_content(viatype -P, with theWORKTRUNK_BINoverride). This mirrors the existingmake_zsh_autoload_safetransform applied to the zsh package registration.Changed files:
src/commands/init.rs(emit the safe form for fish, drop the now-unusedEnvFishimport, update the doc comment),src/commands/configure_shell.rs(fish_completion_content→pub(crate)).Testing
wtvendor completion, thenfish -c 'complete -C "wt remove "'recursed to the call-stack limit (in function 'wt' with arguments '-- wt remove ""'repeating). The install-pathtype -Pcompletion did not recurse, isolating the bare-wtregistration as the cause.complete -C "wt remove "and the issue's minimal reprofish -c 'complete -C "wt switch "'both produce completions with no recursion.test_completions_for_all_shells(tests/integration_tests/completion.rs): the fish registration must resolve the binary (type -P/WORKTRUNK_BIN) and must not call the barewtcommand. It fails against the old output and passes with the fix.cargo test --test integration completion(66) andconfigure_shell(48) pass;cargo clippy --bin wtis clean.Closes #3240 — automated triage