Files
tty7/docs/reference/troubleshooting.mdx
T
l0ng-ai 4af4452900 docs(shell-integration): explain why a nested shell stops reporting its cwd (#698)
tty7 injects shell integration into the shell it launches for a pane, and only
that one. A shell the user starts by hand afterwards -- `zsh` typed at a bash
prompt -- is a new process nobody injected into, so it emits no OSC 7 and no
OSC 133, and the pane keeps showing whatever directory the outer shell reported
last.

On a local machine that is invisible, because oh-my-zsh reports OSC 7 itself
from lib/termsupport.zsh. Over SSH it is not: that file returns early when
SSH_CLIENT or SSH_TTY is set, which sshd always sets, so on a remote host
neither oh-my-zsh nor tty7 reports the directory of a hand-started zsh. That
combination is the whole of #698, and it is why the reporter sees it work over
SSH under bash and stop under zsh.

Documented in two places: a "Shells you start yourself" section in the shell
integration reference, and a troubleshooting entry that names the symptom the
way a user would ("the directory stopped following my shell"). Both give the
two remedies -- `chsh -s /bin/zsh` on the remote host, which is what moves the
integration into the shell tty7 bootstraps, and, when the login shell cannot be
changed, a four-line precmd hook that reports OSC 7 by hand. The entry also
says why a login script or `exec zsh` from .bashrc is not a fix: both run after
(or instead of) the shell tty7 set up.

The same pages now describe the process-inspection fallback honestly. It was
one parenthetical -- "tty7 falls back to inspecting the process" -- which
overpromises: it exists only where the daemon can see the pane's processes, so
a local macOS/Linux pane or a remote-workspace pane gets it and an SSH pane or
a Windows pane does not, and it is a poll driven by pane output at most twice a
second, not a report, so it trails a `cd`. That poll is the mechanism behind
the issue's second, unconfirmed half: with tty7-server on the remote host the
daemon is on that host, so a nested zsh's cwd does get picked up out of
/proc -- late, and only when the pane writes something.

Deliberately no code. Propagating the integration into a shell the user starts
by hand would mean exporting ZDOTDIR (or rewriting the user's startup files)
from every pane, which leaks into every zsh in the session including scripts,
and the remote bootstrap deletes its throwaway ZDOTDIR at the first prompt
precisely because an SSH session has no reliable exit hook -- a directory that
outlives its deletion is exactly what would break a nested shell rather than
help it. Tightening the cwd poll for uninstrumented panes is a real
possibility, but it is a Linux-only daemon path that cannot be exercised from
here, for a symptom nobody has yet reproduced; the mechanism is written down
instead.

Claude-Session: https://claude.ai/code/session_01UUyWQXzcBAoBzaSX8pc7nU
2026-09-09 17:59:29 +08:00

207 lines
8.2 KiB
Plaintext

---
title: "Troubleshooting"
description: "The things that go wrong, and what they actually mean."
---
## Start here
```bash
tty7 doctor
```
One table: whether the server is reachable, whether its wire dialect matches
your binary, the three environment variables, pid/uptime/panes, and how many
machine links exist. Most of what follows is a specific answer this gives you.
## `tty7: command not found`
The CLI is put on PATH the first time the app launches. If it is missing:
- Check **Settings → Agents → Install the tty7 command on PATH** is on.
- On Unix it symlinks into whichever of `/opt/homebrew/bin`, `/usr/local/bin`,
`~/.local/bin`, `~/bin`, `~/.cargo/bin` your PATH already covers — if none of
those are on your PATH, add one.
- On Windows the install directory is appended to your user PATH, which needs a
new shell to take effect.
- A `tty7` you installed yourself is never replaced, so an old one earlier in
PATH will win.
Inside a tty7 pane it works regardless, since panes inherit the app's
environment.
## The server is unreachable
`tty7 doctor` says so, and the GUI cannot open panes.
Start it with `tty7 server start`. If you are an agent or a script,
**do not** — tell the user instead. Starting a server they did not ask for
changes what their GUI attaches to.
For logs:
```bash
TTY7_LOG=info # must be set before the server starts
tty7 server logs
```
## Panes came back empty
A crash, a `kill -9`, or a reboot takes the shells with it — that part is
unavoidable. The *screens* should come back: tty7 keeps a capped tail of each
pane's output (256 KiB) and hands it to the pane that reopens on that id.
It is consumed once. If a pane was restored, then closed, then reopened, the
second time there is nothing left to restore — that is by design, not a bug.
## "The background server is still running <build>"
tty7 updated in place, so the app is new and your panes are still served by the
previous build. Restarting the server picks up the new one and **ends every
process in every pane**. There is no hurry — do it when your panes are idle.
[Updates →](/reference/updates)
## A remote machine will not connect
| Message | What it means |
|---|---|
| *"running an old tty7 server that this copy cannot talk to"* | The server there predates your client's protocol. Let tty7 update it — this ends every session on that machine. |
| *"running a newer tty7 server than this copy"* | Update tty7 here instead, or replace the server there. |
| *"answered, but not as a tty7 server"* | Something else is listening, or the binary is not what tty7 expects. |
| *"tty7 no longer has a way to reach <machine>"* | The SSH link dropped. Reconnect from the switcher. |
`tty7 -m <machine>` never dials a fresh connection by design — it uses a link
the local server already holds. Connect from the GUI first.
## <kbd>⇥</kbd> or <kbd>⌃ R</kbd> is not doing what I expect
Both are switches, and turning one off hands the key straight back to your
shell:
- **Settings → Input → Prompt → Tab completion**
- **Settings → Input → Prompt → History search**
If they do nothing at all in a particular pane, the shell there probably has no
[shell integration](/reference/shell-integration) — nushell, elvish, xonsh and
friends run fine but do not get the prompt layer.
## The directory stopped following my shell
The file panel, new splits, and the sidebar's repo grouping all follow the
directory the pane's shell reports (`OSC 7`). If the pane is frozen on one
directory while you `cd` around, something in the pane is not reporting it.
The usual cause is a shell you started by hand. tty7 injects its
[shell integration](/reference/shell-integration) into the shell it launches
for the pane, and nothing else: run `zsh` at a bash prompt and that nested
shell has none. Locally you rarely notice, because oh-my-zsh reports the
directory itself — but its `lib/termsupport.zsh` begins with
```zsh
if [[ -n "$INSIDE_EMACS" || -n "$SSH_CLIENT" || -n "$SSH_TTY" ]]; then
return
fi
```
so over SSH, where sshd sets those, oh-my-zsh does not define the reporter at
all. A nested zsh on a remote host therefore reports nothing, from either
side, and the pane keeps showing wherever the outer shell was.
**Make it the shell tty7 starts.** On the remote host:
```sh
chsh -s /bin/zsh # then reconnect
```
tty7 asks the host for `$SHELL` at connect time and bootstraps *that* shell,
so this is what moves the integration into your zsh — oh-my-zsh keeps working
beside it. Starting `zsh` from a login script, or `exec zsh` from your
`.bashrc`, does not help: both run after (or instead of) the shell tty7 set
up.
**Or report the directory yourself.** If you cannot change the login shell,
add this to the remote `~/.zshrc` — it restores directory tracking only, not
prompt marks or command status:
```zsh
# Report the working directory to tty7 (OSC 7).
autoload -Uz add-zsh-hook
__tty7_cwd() { printf '\e]7;file://%s%s\a' "${HOST:-localhost}" "${PWD//\%/%25}" }
add-zsh-hook precmd __tty7_cwd
```
It is safe to leave in place afterwards: when tty7 *is* integrating that
shell, the two hooks simply report the same directory twice. The bash
equivalent is `PROMPT_COMMAND`:
```bash
__tty7_cwd() { printf '\e]7;file://%s%s\a' "${HOSTNAME:-localhost}" "${PWD//%/%25}"; }
PROMPT_COMMAND="__tty7_cwd${PROMPT_COMMAND:+;$PROMPT_COMMAND}"
```
Two other reasons a pane never reports its directory:
- **The shell is one tty7 does not integrate.** Locally: zsh, bash, fish,
PowerShell, nushell. Over SSH: zsh, bash, fish. A remote host whose
`$SHELL` is anything else gets no bootstrap, silently.
- **You gave the shell your own arguments** in `shell` or a `custom_shells`
entry, which turns the injection off by design.
Where tty7 can see the pane's processes it falls back to reading the
foreground process's working directory, which is why an unintegrated shell in
a **remote workspace** mostly keeps up — that fallback is a poll, though,
sampled while the pane produces output, so it can lag a `cd` until the pane
writes again. There is no such fallback for an **SSH pane** (the processes are
on the far end) or for a local pane on **Windows**.
## <kbd>⌥ B</kbd> types `∫` instead of moving a word
That is macOS's default. Turn on **Settings → Input → Keyboard → Option (⌥) acts
as Meta**.
## CJK characters have a gap on the right
Your CJK fallback advances 1.0em while the primary face advances 0.60205em, so
the glyph does not fill its two-column slot. Install
[Maple Mono NF CN](https://github.com/subframe7536/maple-font) — it is already
first in the fallback chain and fits Hack exactly — or change the primary face.
[The full explanation →](/customization/fonts#cjk-and-the-two-column-grid)
## A theme in my themes folder is not showing up
Settings lists it under **Not loaded from the themes folder**, with the reason.
Usually a missing required key: `background`, `foreground`, `accent`, and `ansi`
(with eight `normal` and eight `bright` entries) are all mandatory.
## My `config.json` edits did nothing
If the file cannot be parsed, tty7 starts on defaults and keeps your original at
`config.json.corrupt` — check for that file. Otherwise:
- An out-of-range number is **clamped**, not applied literally.
- An unrecognised enum value falls back to the default with a log line.
- `scrollback_limit` applies to **new** panes only.
- An unknown action name in `keybindings` is skipped with a warning.
## Selecting text inside vim / less selects the app's own thing
Hold <kbd>⇧</kbd> while dragging to keep the gesture local, or turn off
**Settings → Terminal → Mouse → Report mouse to apps**.
## `tty7 capture … | head -1` printed a Rust panic
An old build's behaviour when the reader hangs up. The data you asked for still
arrived. On such a build, redirect to a file and slice the file instead of
piping into `head`. Current builds exit `141` on Unix, which is exactly what
`cat` does.
## Still stuck
<CardGroup cols={2}>
<Card title="Discord" icon="discord" href="https://discord.gg/s3dethqz2V">
Ask — someone has probably hit it.
</Card>
<Card title="Report an issue" icon="github" href="https://github.com/l0ng-ai/tty7/issues/new">
Include `tty7 doctor` output and your platform.
</Card>
</CardGroup>