Troubleshooting
Start with ws doctor: it checks most of what can go wrong and tells you the
command that fixes each problem. This page lists every check ws doctor
makes, the doctor badge in the top bar, where to find the logs, and what to
do about the problems people run into most: a server from another build,
sockets left by a crash, a subcommand that ran as a program, agents that
show no status, copying text, and keys that don’t arrive.
The prefix key is Ctrl-Space: prefix I means press Ctrl-Space, then
I.
ws doctor
Section titled “ws doctor”ws doctorws doctor prints its checks in groups. Each line starts with a mark:
| Mark | Meaning |
|---|---|
✓ |
Fine. |
! |
A warning: ws works, but something is missing or could be better. |
✗ |
A problem: something ws needs is broken. |
Warnings and problems have a line starting with → underneath, with the fix.
The last line sums up: All good., N warning(s); ws works., or
N problem(s) to fix. The exit code is 1 when there is at least one problem,
so you can use ws doctor in scripts.
ws doctor -L <name> checks the server and saved session for that server
name instead of the default.
ws doctor --json prints the same checks as a JSON array, one object per
check with area, name, level (ok, warn or fail), detail and,
for warnings and problems, fix. The exit code is the same as without --json.
The doctor badge in the top bar
Section titled “The doctor badge in the top bar”A running server runs ws doctor --json in the background when it starts
and every 30 minutes after. While any check fails, the top bar shows
✗ doctor: N. Warnings don’t show a badge. Click the badge, or choose
Run ws doctor in the general menu (right-click the top bar), to open a
tab with the full report. The server checks again about a minute after you
open that tab, so the badge clears soon after you fix the problem. See
The interface.
| Check | Result | Fix |
|---|---|---|
git |
✗ if missing or older than 2.17 (worktrees need it) |
Install or upgrade git. |
nvim |
! if missing; ✗ if older than 0.10 |
brew install neovim |
lazygit |
! if missing |
brew install lazygit |
claude |
! if missing |
Install Claude Code. |
codex |
! if missing (it’s optional) |
Install Codex. |
gh |
! if missing |
brew install gh |
gh auth |
! if gh auth status fails. Checked only when gh is installed. |
gh auth login |
Agent hooks
Section titled “Agent hooks”| Check | Result | Fix |
|---|---|---|
| Claude Code | ! if ~/.claude/settings.json doesn’t exist, has no ws hooks, or is missing some of them (it lists which). ✗ if the hooks run a ws binary that doesn’t exist. ✗ if the file isn’t valid JSON. |
ws integrate claude (add --bin /path/to/ws to point the hooks at a binary that exists). Fix invalid JSON by hand; ws won’t edit it. |
| Codex | The same checks for ~/.codex/hooks.json (or $CODEX_HOME/hooks.json). Checked only when codex is installed. |
ws integrate codex |
| Codex hooks | ✗ if codex features list says hooks is off, so Codex never runs the hooks. ! if ws couldn’t read the list. Checked only when codex is installed. |
ws integrate codex, which sets features.hooks = true in ~/.codex/config.toml. |
| Codex autostart | ✗ if Codex’s daemon_auto_start feature is on: every Codex session then runs through one shared background daemon, which runs the hooks with the environment of the pane that started it, so status lands on the wrong pane (or on one that’s gone). ! if ws couldn’t read the list. |
ws integrate codex, which sets features.daemon_auto_start = false. |
| Codex daemon | ! if Codex’s shared daemon is running (its control socket, ~/.codex/app-server-control/app-server-control.sock, answers). Sessions using it report to the wrong pane. |
codex app-server daemon stop, then start Codex again. Stopping it ends the sessions using it; codex resume picks them up. |
The hooks check passes only when every event ws listens to is hooked: 7
events for Claude Code and 8 for Codex. A hook that runs a bare ws counts
as existing, since it is found through your PATH when it runs.
Config
Section titled “Config”| Check | Result | Fix |
|---|---|---|
| templates | ! if there are no templates in ~/.config/ws/templates/. ✗ if a template file can’t be read or parsed. |
Copy the example in Workstreams there and edit it, or fix the file. |
template <name> |
✗ if the template’s repo isn’t a git checkout. |
Fix repo in the template. |
See Configuration for the template format.
Terminal
Section titled “Terminal”These checks look at the terminal you run ws doctor in. Inside a ws pane
they always pass, because ws sets both variables in its panes, so run
ws doctor from a plain terminal to check the terminal itself.
| Check | Result | Fix |
|---|---|---|
| true color | ! if COLORTERM isn’t truecolor or 24bit. |
Use a true-color terminal, such as Ghostty. |
| TERM | ! if TERM is unset or dumb. |
Run ws in a real terminal. |
| Check | Result | Fix |
|---|---|---|
| socket dir | ✗ if anyone but you has any permission on the socket directory. Anyone who can reach a ws socket can drive your agents. |
chmod 700 <socket dir> |
| server | Says whether the server is running, and on which socket. Never a problem. | |
| sockets | ! if sockets were left by runs that crashed (sockets nothing answers on). |
ws doctor --fix |
| saved session | Says whether there is a saved session and, if the server isn’t running, that ws will offer to restore it. ✗ if the state file can’t be read. |
Move the file aside: mv <file> <file>.bak. |
| worktrees | ! if a workstream in the saved session has a worktree that is gone from disk. Restoring opens those tabs in a fallback directory. Otherwise, a count of the worktrees on disk and how many aren’t open in the saved session. |
git worktree prune, in the repo, tidies git’s records. |
ws doctor –fix
Section titled “ws doctor –fix”ws doctor --fix--fix asks first:
Remove stale sockets and create missing ws directories? [y/N]If you answer y, it:
- removes every socket in the socket directory that nothing answers on;
- creates the state directory (
~/.local/state/ws) if it’s missing.
It prints what it did, then runs the checks as usual. It changes nothing
else. Other fixes, such as ws integrate or chmod, you run yourself. With
no terminal to ask on, --fix does nothing.
The server writes its log to one file:
~/.local/state/ws/ws.log($XDG_STATE_HOME/ws/ws.log when XDG_STATE_HOME is set.) Every server and
--local run appends to it. It records server start and stop, restore
problems, save errors, hook and socket errors, and processes that needed
SIGKILL on quit. When it grows past 10 MB, the next server or --local
run moves it to ws.log.1 (replacing the one before) and starts a new one.
tail -f ~/.local/state/ws/ws.logCrash logs
Section titled “Crash logs”The server has no terminal to print a panic on. When it panics, it writes the
message and a backtrace to ~/.local/state/ws/crash-<time>.log, where
<time> is in Unix seconds, and notes the file in ws.log. Include the
crash log when you report a bug.
A panic inside one pane’s threads marks just that pane as failed; the other panes keep running.
After a crash, your session was saved as it ran. The next ws offers to
restore it. See Sessions.
The server won’t start
Section titled “The server won’t start”ws waits up to 10 seconds for a new server to open its socket. If it
doesn’t, you see one of these:
the ws server exited during startup (...); see ~/.local/state/ws/ws.logthe ws server didn't open <socket> in time; see ~/.local/state/ws/ws.logRead the end of ws.log for the reason. A common one is a command that
doesn’t exist: ws somecommand starts a server whose first pane runs
somecommand, and the server stops if it can’t start it.
A subcommand ran as a program
Section titled “A subcommand ran as a program”Any word after ws that isn’t a subcommand is a program to run. So a typo,
or a subcommand quoted as one word, doesn’t give a usage error. Instead,
ws starts a server (or attaches to the running one) and tries to run it in
a pane:
ws "tab new" # wrong: runs a program named "tab new"ws tabs list # wrong: "tabs" isn't a subcommandws tab new # right: separate words, spelled as in `ws --help`If this happens, close the pane or tab it opened. If it started a new server
that you don’t want, stop it with ws kill-server. The subcommands are
listed in Command reference.
“This server is from another ws build”
Section titled ““This server is from another ws build””After you rebuild or reinstall ws, attaching to a server that is still
running the old build shows this in the top bar:
this server is from another ws build: `ws restart` switches, keeping the sessionThe old server keeps working. To switch to the new build:
ws restartThis stops every pane’s processes and starts a server on the new build that puts the layout back, with agents resuming their conversations. Commands that were running in shells need starting again. See Restart, keeping the session.
“The running ws server is too old for this command”
Section titled ““The running ws server is too old for this command””A newer ws command talked to an older server that doesn’t know the
command, for example a scripting command added in a later build:
the running ws server is too old for this command (...); install the new build, then `ws kill-server` and `ws`The fix is to restart the server on the new build, as above. If the old
server doesn’t know ws restart either (it says it’s too old), use
ws kill-server and then ws. That doesn’t keep the session, so let your
agents finish first. Some
scripting commands say it may be too old when the server’s reply is missing
something they need; the fix is the same.
Stale sockets
Section titled “Stale sockets”When a server or a --local run crashes, its socket file stays behind.
Nothing answers on it. This is mostly harmless:
- A new server removes a stale socket with its own name before it starts.
ws doctorwarns about the rest, andws doctor --fixremoves them.
If a command says no ws server is running on <socket> or
no ws server answers on <socket>, the server for that name isn’t running.
Start it with ws, or check that you are using the right -L name.
If starting a server fails with <socket> is already in use, a server with
that name is running already. Attach to it with ws attach.
An agent shows no status
Section titled “An agent shows no status”An agent that ws found by its process name but that has never sent a hook
event shows no status, with what to do next (no status: ws integrate claude, or for Codex no status: see ws doctor). Work through these in order:
-
Are the hooks installed? Run
ws doctorand look at “Agent hooks”. Install them if needed:Terminal window ws integrate claude -
Was the agent running before you installed the hooks? Agents read their hooks when they start. Quit the agent and start it again.
-
For Codex, are its features right, and is its daemon stopped?
ws doctorchecks “Codex hooks”, “Codex autostart” and “Codex daemon”. Codex’s shared daemon runs every session’s hooks as if they came from the pane that started it, so they report the wrong pane.ws integrate codexsets the features; stop a running daemon withcodex app-server daemon stopand start Codex again. -
Do the hooks run a
wsthat exists? If you installed hooks from a build that has since moved or been deleted,ws doctorshows✗with “hooks run …, which doesn’t exist”. Runws integrateagain from the installedws, or pass--bin. -
Is the agent running in a
wspane? Hooks report to the server throughWS_SOCKETandWS_PANE_ID, which onlywspanes have. Outsidews, the hooks do nothing, on purpose.
Look at what ws saw
Section titled “Look at what ws saw”The inspector shows why an agent has the status it has. Open it with
prefix I for the focused pane, i on an agent in the sidebar, or “Inspect
agent status” in the pane’s or tab’s right-click menu. It shows:
- the status, how long it has held, and what set it: a hook event, a process detection, or you;
- the agent’s identity: kind, model, session id, whether hooks are reporting, and where its label came from;
- the pane’s foreground process and its children;
- a timeline of the latest hook events, status changes and detections.
In the inspector, c copies a report of everything shown to the clipboard
and saves it as ~/.local/state/ws/status-report-<pane>-<time>.txt, and
d, w, b or x set the status by hand until the agent’s next real
event. Agents covers the rest.
The same history is kept in the state file, so you can read it from any shell, even when the server isn’t running:
ws debug events --since 10mws debug events --since 2h --pane 3ws debug status 3ws debug events lists hook events, status changes and detections, oldest
first. --since takes a number with s, m, h or d (default 10m), and
--pane limits it to one pane; the pane number is in the inspector’s title.
ws debug status <pane> shows a pane’s saved agent identity and its last 20
events. Times are shown in UTC. Add -L <name> to read another server’s
history.
If there are no hook events at all for the pane, the hooks aren’t reaching
ws: go back to the checklist above. If events arrive but the status is
wrong, the timeline shows which event set it; include the inspector report
(c) when you report it.
“Could not resolve to a Repository” when reviewing a PR
Section titled ““Could not resolve to a Repository” when reviewing a PR”GitHub says this when the gh account in use can’t see the repo, typically a
private org repo while your active account is a personal one. ws review
tries your other gh logins itself, and the review tab uses the one that
works. If it still fails:
- Run
gh auth statusand check that an account with access is logged in; if not,gh auth login. - Or name the account in
~/.config/ws/config.toml:[review.repos.<alias>] gh_user = "<account>".
See Reviewing PRs.
Copying text
Section titled “Copying text”Drag across text in a pane to select it. When you let go, ws copies it to
your clipboard. Double-click selects a word (paths and URLs included), and
triple-click selects a line. To select with the keyboard, or text that has
scrolled off the screen, use copy mode (prefix [); see
Keys and mouse.
ws copies through your terminal, with the OSC 52 escape sequence, so it
works over SSH too. Your terminal has to support OSC 52 and allow it:
- Ghostty, iTerm2, kitty and WezTerm support it. Some terminals ask before letting programs write the clipboard, or have a setting for it; in iTerm2, look for the setting that lets terminal apps access the clipboard.
- Terminal.app doesn’t support OSC 52.
- If you run
wsinside tmux, tmux has to pass clipboard writes on (itsset-clipboardoption).
If nothing lands on your clipboard, try another terminal first.
Selecting in nvim, lazygit and other mouse programs
Section titled “Selecting in nvim, lazygit and other mouse programs”Programs that use the mouse themselves, such as nvim and lazygit, get your
clicks and drags as usual. Hold Shift while you drag to select text in
ws instead. Some terminals use Shift-drag for their own selection; if
yours does, you get the terminal’s selection, which doesn’t know about pane
borders.
Typing or clicking clears the selection.
Keyboard problems
Section titled “Keyboard problems”The prefix does nothing
Section titled “The prefix does nothing”- On macOS,
Ctrl-Spaceis also the default shortcut for switching input sources. If it’s taken, turn that shortcut off in System Settings, under Keyboard, Keyboard Shortcuts, Input Sources. - To send a literal
Ctrl-Spaceto the program in a pane, press it twice:prefix Ctrl-Space.
Alt keys don’t work on macOS
Section titled “Alt keys don’t work on macOS”Keys such as Alt-h and Alt-1 need your terminal to send Option as Alt.
In Ghostty, set:
macos-option-as-alt = trueOther terminals have a similar setting.
Alt-[ doesn’t work, or Shift-Enter doesn’t reach the agent
Section titled “Alt-[ doesn’t work, or Shift-Enter doesn’t reach the agent”Without the kitty keyboard protocol, some keys send the same bytes as others:
Alt-[sends the same bytes as the start of an escape sequence.- Shift-Enter and similar keys look like their plain versions.
When your terminal supports the kitty keyboard protocol, ws turns it on, and
these keys come through distinctly. Ghostty, kitty and WezTerm support it;
tmux and Terminal.app don’t. In other terminals, use Alt-] or prefix 1 to
9 to change tabs.
Still stuck
Section titled “Still stuck”Collect these before you ask for help or file a bug:
- the output of
ws doctor; - the end of
~/.local/state/ws/ws.log, and anycrash-*.logfiles; - for a status problem, the inspector report (
cin the inspector) or the output ofws debug events --pane <n>.
See also Sessions, Agents and Configuration.