---
title: Daemon consumers
description: First consumers of the gwm daemon — a compact statusline for shell prompts, the raw JSON-RPC one-liner, and an editor recipe (Zed / VS Code).
navigation:
title: Daemon consumers
---
# Daemon consumers
[`gwm daemon`](/cli/reference#gwm-daemon-socket-path-poll-ms-ms-issue-38) 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 (`cd` into any worktree of the repo, then `gwm 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's `gwm statusline --socket` at the same path) — otherwise the second `gwm daemon` is refused by the live-socket guard.
## Start the daemon
```bash
# 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 A
gwm 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`
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:
```text
$ gwm statusline
feat/#309-daemon-consumer · 3 wt · * ↑1 · #309 · PR #310
```
The 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 a `gh` call on every prompt redraw. It is left as a follow-up rather than smuggled into the wire protocol.
### Live updates — `--watch`
`--watch` subscribes to the daemon's `worktrees.changed` stream and reprints the line on every change, one line per update — ideal for a long-running command feeding a statusbar:
```bash
gwm statusline --watch
```
### 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
zsh — right prompt (refreshed on each prompt; one socket round-trip to the running daemon):
```bash
# ~/.zshrc
function gwm_rprompt() { gwm statusline 2>/dev/null }
setopt PROMPT_SUBST
RPROMPT='$(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):
```bash
# ~/.tmux.conf
set -g status-interval 5
set -ag status-right ' #(cd #{q:pane_current_path} && gwm statusline)'
```
starship — custom command:
```toml
# ~/.config/starship.toml
[custom.gwm]
command = "gwm statusline"
when = true
shell = ["bash", "--noprofile", "--norc"]
format = "[$output]($style) "
```
## 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.
```bash
# socat
printf '{"jsonrpc":"2.0","method":"list","id":1}\n' \
| socat - UNIX-CONNECT:"${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm.sock"
# nc / netcat
printf '{"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/`](https://github.com/kbrdn1/gwm-cli/tree/main/docs/schema) and the [`gwm daemon` reference](/cli/reference#gwm-daemon-socket-path-poll-ms-ms-issue-38).
## 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
`.zed/tasks.json` — a task that prints the live statusline and one that lists the worktrees through the socket:
```json
[
{
"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
`.vscode/tasks.json` — same idea, using the daemon socket to list worktrees:
```json
{
"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`:
```bash
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.