mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-05-29 10:31:14 -03:00
Pass job group down to builtin_print_help
Repro:
$ abbr -h | less
fish: Job 1, 'abbr --help | less' has stopped
Bisects to 5ece9bec6c (__fish_print_help: use man as-is, 2025-08-29).
Root cause is similar to
https://github.com/fish-shell/fish-shell/issues/8699#issuecomment-1030762748
except it's specific to how __fish_print_help is invoked, since
"__fish_print_help abbr | less" works fine.
Fix this by letting __fish_print_help access the terminal.
Fixes #12612
Solved by Claude in a single prompt:
---
▐▛███▜▌ Claude Code v2.1.92
▝▜█████▛▘ Sonnet 4.6
▘▘ ▝▝ ~/git/fish-shell
❯ "abbr -h | less" gets a signal, resulting in the message "fish: Job 1, 'abbr -h | less' has stopped"
whereas "__fish_print_help abbr | less" works just fine, even though "abbr -h" should run exactly that.
Why this difference?
[...]
● Now I can give you a clear answer.
---
The difference comes down to job group inheritance when man is
spawned.
The deferred execution order
In abbr -h | less, fish uses the "deferred process" optimization:
since abbr is a non-external process piping into an external one,
fish launches less first,
transfers the terminal to less's process group, then runs the
deferred abbr builtin. By the time abbr's help function spawns man,
the terminal already
belongs to less's pgroup.
Same thing happens with __fish_print_help abbr | less — the outer
less is launched first and gets the terminal.
Where the paths diverge
Function case (__fish_print_help abbr | less):
get_performer_for_function (exec.rs:1050) captures job.group.clone()
and passes it to eval_node. So when command man runs inside the
function, it's given the outer pipeline's job group — the same
one that owns the terminal. man's inner less pager joins that group
and can read from the terminal freely. ✓
Builtin case (abbr -h | less): builtin_print_help calls
parser.eval(&cmd, streams.io_chain) (shared.rs:652). parser.eval
is defined as:
pub fn eval(&self, cmd: &wstr, io: &IoChain) -> EvalRes {
self.eval_with(cmd, io, None, BlockType::top, false)
// ^^^^ job_group = None
}
job_group = None means man creates a brand-new job group with no
terminal access. When man spawns its less pager, the pager tries
to read keyboard input from
the terminal, but the terminal belongs to the outer less's process
group. The pager receives SIGTTIN → stops → the whole job is
reported as stopped.
The fix
builtin_print_help should pass streams.job_group to the eval call
(which is already populated at exec.rs:1192). Changing line 652 from:
let res = parser.eval(&cmd, streams.io_chain);
to:
let res = parser.eval_with(&cmd, streams.io_chain, streams.job_group.as_ref(), BlockType::top, false);
would give man the outer job's job group, letting its pager access
the terminal just like the function case.
✻ Sautéed for 13m 0s
This commit is contained in:
@@ -649,7 +649,13 @@ pub fn builtin_print_help(parser: &Parser, streams: &mut IoStreams, cmd: &wstr)
|
||||
}
|
||||
let name_esc = escape(cmd);
|
||||
let cmd = sprintf!("__fish_print_help %s ", &name_esc);
|
||||
let res = parser.eval(&cmd, streams.io_chain);
|
||||
let res = parser.eval_with(
|
||||
&cmd,
|
||||
streams.io_chain,
|
||||
streams.job_group.as_ref(),
|
||||
BlockType::top,
|
||||
false,
|
||||
);
|
||||
if res.status.normal_exited() && res.status.exit_code() == 2 {
|
||||
streams
|
||||
.err
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#RUN: %fish %s
|
||||
#REQUIRES: command -v tmux
|
||||
#REQUIRES: command -v less
|
||||
|
||||
isolated-tmux-start -C '
|
||||
set -g fish_autosuggestion_enabled 0
|
||||
@@ -8,6 +9,14 @@ isolated-tmux-start -C '
|
||||
abbr -g abbr-test "abbr-test [expanded]"
|
||||
'
|
||||
|
||||
isolated-tmux send-keys "abbr --help | sed 1q | less" Enter
|
||||
tmux-sleep
|
||||
isolated-tmux capture-pane -p
|
||||
# CHECK: (END)
|
||||
# CHECK: prompt 0> abbr --help | sed 1q | less
|
||||
# CHECK: Documentation for abbr
|
||||
isolated-tmux send-keys q
|
||||
|
||||
# Expand abbreviations on space.
|
||||
isolated-tmux send-keys abbr-test Space arg1 Enter
|
||||
tmux-sleep
|
||||
|
||||
Reference in New Issue
Block a user