Skip to content

Don't cancel on input during remote file completion - #53

Merged
jojojames merged 3 commits into
jojojames:mainfrom
Silex:tramp-completion-hang
Sep 16, 2026
Merged

jojojames merged 3 commits into
jojojames:mainfrom
Silex:tramp-completion-hang

Conversation

@Silex

@Silex Silex commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Typing /ssh:user@host:/srv/ at normal speed on a cold connection hangs Emacs until I kill
the ssh process. C-g doesn't help.

fussy-all-completions wraps the table call in while-no-input. For a remote file name that
call is a Tramp connection attempt, so aborting it doesn't just throw away a result, it
interrupts the remote shell handshake partway through.

SIGUSR2 backtrace:

vertico--exhibit
  vertico--update(interruptible)
    vertico--filter-completions
      completion-all-completions
        completion--nth-completion (styles: fussy basic partial-completion emacs22)
          completion-basic-all-completions
            read-file-name-internal
              file-name-all-completions "" "/scp:root@host:"
                tramp-sh-handle-file-name-all-completions
                  file-directory-p "/scp:root@host:/root/"
                    tramp-send-command
                      tramp-wait-for-output
                        tramp-wait-for-regexp(proc nil "\\(?:^\\|\0\\)\\(?:[^\n#$]*///cef0db9c…#\\$…\\)")

The timeout there is nil, which per tramp.el:6547-6574 means wait forever. The connection
buffer contained:

# tramp_exit_status 0
# 

That's a plain sh prompt, not Tramp's ///<hash>#$ marker. The throw happened before PS1
got set, so the marker never arrives.

Vertico avoids this on its side (vertico.el:374-379 skips while-no-input and non-essential
for remote paths, minad/vertico#23), but fussy's wrap is inside the completion style, so that
guard doesn't apply.

#48

Probably the same line. while-no-input is (or (input-pending-p) (progn ,@body)), so when
input is already pending the body never runs and you get t back, which matches "looks frozen
until you press down". f582245/1cb4bb4 made that branch return fussy--current-result instead
of nil, which fixes the display. It doesn't help here, since the damage is the abort rather
than the return value. I opened this separately because the failure mode is different, but
feel free to fold them together.

The change

fussy-cancel-on-input-fn goes from minibufferp to fussy-cancel-on-input-p, which is the
same thing minus remote file name completion. Gating on minibuffer-completing-file-name keeps
everything else abortable when default-directory is remote.

I used vertico's regexp instead of file-remote-p: it matches once the method is typed, which
is earlier, and it skips the file name handler.

Downside is that scoring can't be interrupted while completing in a remote directory, so a huge
remote directory will feel slower. (setq fussy-cancel-on-input-fn #'minibufferp) gets the old
behaviour back.

Repro

vertico + fussy, any host you can ssh to:

  1. M-x tramp-cleanup-all-connections and delete ~/.cache/emacs/tramp.* so the connection is
    actually cold.
  2. C-x C-f, then type /ssh:user@host:/srv/ by hand at normal speed.

You have to be typing while the connection is coming up. Pasting the whole path never triggers
it, which is probably why it feels random.

I tested by driving a TUI Emacs through a pty at 0.15s/char. Hangs every time on current main,
passes every time on this branch, and hangs again if I set the default back to minibufferp on
the branch. /scp: and /ssh: behave the same, so the method isn't relevant. Emacs 31.1,
Tramp 2.8.2, fuz-bin.

compile/lint/test pass, same four warnings as main. Added tests, plus a paragraph in
architecture.org since it currently describes the wrap as unconditional.

If you'd rather leave the default alone I can just add the predicate and document it.

For a remote file name the completion table call is a Tramp connection
attempt. Aborting it with while-no-input unwinds out of the middle of
the remote shell handshake, and Tramp is then left in
tramp-wait-for-regexp with a nil timeout, waiting for a prompt marker
the half-configured shell never prints. Emacs blocks until the
connection process is killed.

Vertico declines to interrupt completion for remote paths for this
reason; make fussy-cancel-on-input-fn default to the same rule.
Gating on minibuffer-completing-file-name keeps everything but file
completion abortable while a remote directory is current.
@jojojames

Copy link
Copy Markdown
Owner

Thanks, I attempted to figure this out a while back but like you mentioned it's hard to reproduce.

I mostly use fzfa-tramp or fzfa-ssh these days for any tramp related mechanisms so it's rare for me to exercise this path.

@jojojames
jojojames merged commit 4e2a5e7 into jojojames:main Sep 16, 2026
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.

2 participants