Daemon consumers
gwm daemon is a long-running JSON-RPC 2.0 server over a unix domain socket (a named pipe on Windows, #439): editors, statusbars, and tooling connect once and call list / doctor / path, or subscribe for pushed worktrees.changed notifications, instead of spawning gwm per query. This page covers the consumer side: the bundled gwm statusline, the raw protocol one-liner, and an editor recipe.
The daemon is one per repo: it answers for the repository it was launched in. Start it from inside the worktree set you want to watch (
cdinto any worktree of the repo, thengwm daemon). The default socket path is shared, so to run daemons for several repos at once give each a distinct--socket(and point that repo’sgwm statusline --socketat the same path); otherwise the secondgwm daemonis refused by the live-socket guard.
Start the daemon
Section titled “Start the daemon”# Binds $XDG_RUNTIME_DIR/gwm.sock (→ $TMPDIR → /tmp on macOS, where# XDG_RUNTIME_DIR is unset). Override with --socket.## Security note: when the chosen base dir is NOT owner-only (the shared# /tmp last resort), the socket is isolated in a per-user owner-only# sub-dir instead — $base/gwm-<uid>/gwm.sock — so another local user can't# connect. The common $XDG_RUNTIME_DIR / per-user $TMPDIR paths (already# private) are unchanged. The daemon prints its actual bound path to# stderr ("gwm daemon listening on <socket>"), so a raw nc/socat consumer# in that fallback case should read the path from there rather than# hard-coding $base/gwm.sock.gwm daemon
# A faster subscribe cadence (default 1000 ms):gwm daemon --poll-ms 300
# Several repos at once: one socket each (the default path is shared, so a# second daemon on it is refused). Point each consumer at the matching path.gwm daemon --socket "${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm-api.sock" # in repo Agwm statusline --socket "${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm-api.sock"The daemon prints gwm daemon listening on <socket> to stderr only once the socket is bound, so a wrapper can treat that line as a readiness signal.
Statusline: gwm statusline
Section titled “Statusline: gwm statusline”The first bundled consumer. It connects to the daemon, asks for the worktree set, and renders a single compact line for a tmux / starship / zsh prompt:
$ gwm statuslinefeat/#309-daemon-consumer · 3 wt · * ↑1 · #309 · PR #310The line is built only from the daemon’s stable schema:
| Token | Meaning |
|---|---|
feat/#309-… |
active worktree’s branch (or its name when detached) |
3 wt |
total worktree count |
* |
the active worktree has uncommitted changes |
↑n / ↓n |
commits ahead of / behind the upstream |
#309 |
linked issue number |
PR #310 |
linked PR number |
claude |
an active AI-agent session in the active worktree (from the experimental agents field, #408). Idle sessions are not advertised here; the TUI overlay (a) has the full list. |
The active worktree is the one whose directory encloses the current working directory; outside any worktree the line collapses to just the count (3 wt).
No CI rollup. The
CI passing 9/9-style half of the original sketch is intentionally omitted: a CI rollup is not part of the daemon’s stable schema, and fetching it would mean aghcall on every prompt redraw. It is left as a follow-up rather than smuggled into the wire protocol.
Live updates: --watch
Section titled “Live updates: --watch”--watch subscribes to the daemon’s worktrees.changed stream and reprints the line on every change, one line per update, which is ideal for a long-running command feeding a statusbar:
gwm statusline --watchGraceful degradation
Section titled “Graceful degradation”When no daemon is reachable, gwm statusline prints an empty line and exits 0, so a prompt substitution degrades to nothing instead of showing an error. That makes it safe to drop into a prompt unconditionally.
Prompt recipes
Section titled “Prompt recipes”zsh, right prompt (refreshed on each prompt; one socket round-trip to the running daemon):
function gwm_rprompt() { gwm statusline 2>/dev/null }setopt PROMPT_SUBSTRPROMPT='$(gwm_rprompt)'tmux status-right, re-queried on the status interval (for push-driven updates instead of polling, run gwm statusline --watch into a pane and read its tail):
set -g status-interval 5set -ag status-right ' #(cd #{q:pane_current_path} && gwm statusline)'starship, custom command:
[custom.gwm]command = "gwm statusline"when = trueshell = ["bash", "--noprofile", "--norc"]format = "[$output]($style) "Raw protocol: the thin proof
Section titled “Raw protocol: the thin proof”No gwm binary required on the consumer side: the wire format is newline-delimited JSON, one request per line, one response per line. Anything that can write a line to a unix socket is a client.
# socatprintf '{"jsonrpc":"2.0","method":"list","id":1}\n' \ | socat - UNIX-CONNECT:"${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm.sock"
# nc / netcatprintf '{"jsonrpc":"2.0","method":"list","id":1}\n' \ | nc -U "${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm.sock"
# subscribe — holds the connection open and streams a `worktrees.changed`# notification on every change (the first line is the current snapshot).# `cat` keeps the write side open so the daemon keeps streaming; without it# `printf` exits, the socket half-closes, and only the first snapshot# arrives. Ctrl-C to stop.{ printf '{"jsonrpc":"2.0","method":"subscribe","id":1}\n'; cat; } \ | socat - UNIX-CONNECT:"${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm.sock"Methods: list (worktree array), doctor (health report with severity + exit_code), path ({"params":{"pattern":"<fuzzy>"}} → { name, path, branch }), and subscribe (upgrades the connection to a one-way notification stream). Agent-session changes push too: a session appearing, vanishing, flipping active ↔ idle, or writing fresh activity (last_activity) triggers a worktrees.changed. Unlike age_seconds, last_activity only moves on real agent writes, and the 30 s detection cache bounds the push frequency. The response schemas are the same stable DTOs the --format=json CLI flags emit. See docs/schema/ and the gwm daemon reference.
Editor recipe
Section titled “Editor recipe”An editor doesn’t need a plugin: a task that shells out to the daemon (or to gwm’s --format=json surface) is enough to list worktrees and jump between them.
.zed/tasks.json, a task that prints the live statusline and one that lists the worktrees through the socket:
[ { "label": "gwm: statusline", "command": "gwm statusline", "use_new_terminal": false, "reveal": "always" }, { "label": "gwm: worktrees (via daemon)", "command": "printf '{\"jsonrpc\":\"2.0\",\"method\":\"list\",\"id\":1}\\n' | socat - UNIX-CONNECT:\"${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm.sock\" | jq -r '.result[] | \"\\(.name)\\t\\(.path)\"'", "reveal": "always" }]VS Code
Section titled “VS Code”.vscode/tasks.json, the same idea, using the daemon socket to list worktrees:
{ "version": "2.0.0", "tasks": [ { "label": "gwm: worktrees (via daemon)", "type": "shell", "command": "printf '{\"jsonrpc\":\"2.0\",\"method\":\"list\",\"id\":1}\\n' | socat - UNIX-CONNECT:\"${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm.sock\" | jq -r '.result[] | \"\\(.name)\\t\\(.path)\"'", "problemMatcher": [] } ]}To jump to a worktree by fuzzy pattern, call path instead of list:
printf '{"jsonrpc":"2.0","method":"path","params":{"pattern":"309"},"id":1}\n' \ | socat - UNIX-CONNECT:"${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm.sock" | jq -r '.result.path'Without a running daemon, the same data is one gwm invocation away (gwm list --format=json and gwm path <pattern> --format=json emit the identical schemas), but the daemon avoids a process spawn per query.