It's a race condition.
Expanding #{pane_current_path}
requires the tmux server to learn the current working directory of the process being the "main one" in the pane. It doesn't matter how the tmux server learns or already knows which process this is, the point is the process is not a part of tmux, it's a separate process.
Often, like in your case, the "main process" in a pane is an interactive shell.
Tmux server may learn the current working directory of this shell by checking /proc/<PID>/cwd
, where <PID>
denotes the PID of the shell; or by some equivalent method.
When you type cd whatever
Enter and your send-key Enter; refresh -S
works, send-key Enter
makes the shell start interpreting cd whatever
. The important thing is send-key
just sends the key, it does not wait for any effect of this action; and the shell just starts interpreting cd whatever
, interpreting and executing is going to take some time; but without any delay tmux proceeds to refresh -S
which makes the tmux server start refreshing the client's status line, which involves evaluating #{pane_current_path}
. The shell (triggered by send-key
) and the tmux server (triggered by refresh
) do their tasks in parallel and there is no guarantee the shell manages to actually change its current working directory before #{pane_current_path}
gets evaluated.
If the format string is evaluated first then you will see the old working directory in the status line.
Another command or a blank line shortly after the cd
makes #{pane_current_path}
return the new current working directory because Enter here is late enough, the shell has managed to change its working directory.
A good way to cause cd
in your interactive shell to refresh the status line without a race is to make cd
itself trigger refresh -S
after it actually changes the working directory:
if [ -n "$TMUX" ]; then cd () { command cd "$@" ( status="$?" tmux refresh -S return "$status" ) }fi
If the shell is inside tmux then the above snippet will create a shell function that overloads the cd
builtin.
Notes:
- The shell code itself is portable. The only non-portable part is the invocation of
tmux
. - Existence of an alias for
cd
will make the snippet fail or misbehave. In Bash you can solve this problem by replacingcd ()
with non-portablefunction cd
. - The function retains the exit status of the actual
cd
. - Invoking
tmux
asynchronously (tmux refresh -S &
) will make the shell not wait for it, while not breaking the solution. I'm pointing this out because in your original attempt withbind
the shell also does not wait, so maybe this is what you prefer. But if the asynchronoustmux
fails (very unlikely though) and prints an error message then the message may appear after the next shell prompt, which will be confusing. For this reason IMO it's more elegant to invoketmux
synchronously. It's true that invokingtmux
asynchronously may save few milliseconds; most likely in an interactive shell you won't notice the difference though.