SpyBara
Go Premium

Documentation 2026-08-19 18:02 UTC to 2026-09-03 16:59 UTC

2 files changed +283 −1. View all changes and history on the product overview
2026
Mon 21 23:00 Tue 15 14:01 Fri 11 21:00 Mon 7 16:01 Thu 3 16:59

features/status-line.md +281 −0 created

Details

1#### Features

2 

3# Status Line

4 

5The status line is an optional row at the bottom of Grok Build, above the shortcuts bar. It shows live session values, such as the model, context window usage, and cost, or the output of a script you provide. Grok sends your script the current session data as JSON, so a few lines of shell are enough to build a display of your own.

6 

7Use a status line to watch values that change as you work:

8 

9* How full the context window is, and how close the session is to compacting

10* The branch and repository each session is working in

11* The session's running cost

12* A value from outside the session, such as a CI run, refreshed on a timer

13 

14The status line is off by default. The sections below describe [each mode](#set-up-the-status-line), [how Grok runs your script](#how-the-status-line-works), [every field it sends](#available-data), and [a few scripts to start from](#examples).

15 

16## Set up the status line

17 

18Add a `[ui.status_line]` section to `~/.grok/config.toml` and restart Grok. The `type` key selects the mode: `builtin` shows values Grok renders itself, `command` runs your script, and `disabled` shows nothing.

19 

20Because a `command` status line runs a program, Grok reads this section only from your own configuration or from configuration your administrator manages. A cloned repository cannot set one.

21 

22### Show built-in items

23 

24The `builtin` type fills the row with named items. Items appear in the order you list them, and long values shorten to an ellipsis.

25 

26```toml customLanguage="toml"

27[ui.status_line]

28type = "builtin"

29items = ["cwd", "model", "context"] # default when omitted

30```

31 

32The default set renders as, for example, `my-project │ Grok 4.5 │ 12% ctx`.

33 

34| Item | Description |

35| --- | --- |

36| `cwd` | The name of the current directory. |

37| `model` | The model's display name. |

38| `context` | Context window usage, as a percentage. The value turns amber at the auto-compaction threshold, or at 80 percent when the agent does not report one. |

39| `cost` | The session cost. Hidden below $0.005. |

40| `turn-timer` | The elapsed time of the current turn. Appears after one second. |

41| `session-name` | The session name, if set. |

42 

43### Run your own script

44 

45Set `type` to `command` and point `command` at a script path or an inline shell command. Pipes work as written, and a leading `~/` expands to your home directory. These recipes are POSIX shell, tested on macOS and Linux; on Windows a `command` status line is untested, and the fallback that runs a `.sh` file with no valid `#!` line is not available there.

46 

471. Save a script, for example `~/.grok/statusline.sh`, that reads JSON from standard input and prints a line. This example uses [`jq`](https://jqlang.org/) to extract two fields:

48 

49 ```bash customLanguage="bash"

50 #!/bin/sh

51 payload=$(cat)

52 model=$(printf '%s' "$payload" | jq -r '.model.display_name // "?"')

53 ctx=$(printf '%s' "$payload" | jq -r '.context_window.used_percentage // 0')

54 printf '%s │ %s%% ctx\n' "$model" "$ctx"

55 ```

56 

572. Make it executable with `chmod +x ~/.grok/statusline.sh`.

58 

593. Set it as the status line command:

60 

61 ```toml customLanguage="toml"

62 [ui.status_line]

63 type = "command"

64 command = "~/.grok/statusline.sh"

65 ```

66 

674. Restart Grok. The row appears once the session view is active.

68 

69### Turn it off

70 

71Set `type` to `disabled` to show nothing. `off`, `none`, and `hidden` are accepted spellings of `disabled`. Removing the `[ui.status_line]` section has the same effect, because the status line is off by default.

72 

73### Options

74 

75| Key | Type | Default | Description |

76| --- | --- | --- | --- |

77| `type` | string | `disabled` | `builtin`, `command`, or `disabled`. |

78| `items` | array | `["cwd", "model", "context"]` | Built-in segments, in order. |

79| `command` | string | none | The script or shell command for `type = "command"`. |

80| `padding` | integer | `0` | Horizontal spacing, in characters per side, up to 16. |

81| `refresh_interval` | integer | unset | Seconds between timed re-runs of a `command` script, from 1 to 86,400. When unset, the script runs only on session changes. Ignored under `builtin` and `disabled`, where it schedules nothing and is surfaced only by `grok inspect`. See [Refresh on a timer](#refresh-on-a-timer). |

82 

83## How the status line works

84 

85Grok runs your command and writes the session data to the command's standard input, as one JSON object followed by a newline. What the command writes to standard output becomes the row. Each run is a fresh process, so edits to your script apply on the next update.

86 

87**When it updates.** The script runs when session state changes and continuously while a turn runs, not on a timer. State changes include:

88 

89* The session starting, or a client attaching to it

90* A turn ending

91* A model or reasoning effort switch

92* A commit or a branch switch

93* A compaction

94 

95Updates are spaced at least 300 milliseconds apart, so a busy turn cannot run your script constantly. A change that must appear immediately, such as a window resize, waits about 100 milliseconds. A run that is already going is never cancelled. The next change waits for it to finish. An idle session does not run your script again, so a clock in the output will not tick on its own unless you set [`refresh_interval`](#refresh-on-a-timer).

96 

97**What your script can output.**

98 

99* Up to five lines, each cut at 1024 characters. Escape sequences count toward the limit. On a short terminal, extra lines drop from the bottom.

100* ANSI colors are supported. Every other escape sequence, such as cursor movement, is removed.

101* OSC 8 hyperlinks are supported for `http`, `https`, and `mailto` targets. Other targets render as plain text.

102* Output past 64 KiB is truncated and the script is stopped.

103* A script that succeeds and prints nothing removes the row for that update rather than falling back to the built-in items.

104 

105**How big the row is.** The `COLUMNS` and `LINES` environment variables describe the row your output fills, not the terminal window. Padding is already deducted. `LINES` reads `1` until you print more, and never exceeds five.

106 

107**The environment your script runs in.** Scripts run in the session's working directory. When that is unavailable, Grok uses the repository root, then its own directory. Each run has a 10-second limit, and a script that exceeds it shows `[status line: timed out]`. No shell startup files run: Grok clears `BASH_ENV` and `ENV`, though a shell may still read its own environment file, such as zsh with `~/.zshenv`, so keep expensive setup out of those. `GIT_OPTIONAL_LOCKS=0` is set so read-only `git` commands skip taking the optional index lock and do not collide with the agent's own git operations. When a run ends, for any reason, Grok terminates every process the script started, including processes left in the background.

108 

109## Refresh on a timer

110 

111Set `refresh_interval` on a `command` status line to also re-run the script on a fixed schedule. This lets a value from outside the session, such as a CI result, reach the row while the session is idle.

112 

113```toml customLanguage="toml"

114[ui.status_line]

115type = "command"

116command = "~/.grok/statusline.sh"

117refresh_interval = 300 # seconds

118```

119 

120* The `trigger` field in the JSON says why the script ran: `"refresh_interval"` for a timed run, `"state"` otherwise. A script that calls a network service should fetch on timed runs and read a cached copy on state runs. A busy turn re-runs the script continuously; those are state runs, except that when a timed fire comes due mid-turn, the run that carries it is stamped `"refresh_interval"`.

121* A timed run receives the session data from the most recent state change, so values such as cost and context usage are as of that change. Only what your script fetches itself is current.

122* When a timed run fails or times out, the row keeps its last output rather than showing an error, so an unreliable service does not disturb it. The exception is a script that has not answered yet, as in a fresh session or right after switching agents, where the first failure shows at once because there is nothing to keep. After three consecutive failures, the error shows regardless. A run triggered by session state reports its failure immediately.

123* Timed runs that come due while the row is hidden, or while an earlier run is still going, combine into a single run. Grok never runs the script several times in a row to catch up.

124 

125## Available data

126 

127Grok writes one JSON object to your script's standard input. The table below lists every field it can contain; nothing else is sent.

128 

129| Field | Description |

130| --- | --- |

131| `cwd`, `session_id` | The working directory and the unique session identifier. |

132| `session_name` | The session's tab name, if set. |

133| `prompt_id` | The UUID of the prompt being processed. Present only during a turn. |

134| `transcript_path` | The path to the session's `updates.jsonl`. |

135| `model.id`, `model.display_name` | The model identifier and display name. |

136| `workspace.current_dir` | The current directory. |

137| `workspace.repo_root` | The repository root. Absent outside a repository. |

138| `workspace.branch` | The checked-out branch. Absent on a detached HEAD. |

139| `workspace.git_worktree` | The worktree name, inside a linked worktree. |

140| `workspace.repo.{host,owner,name}` | The repository identity, parsed from the `origin` remote. |

141| `version`, `schema_version` | The Grok release, and the revision of this JSON shape. Test `schema_version` with `>=`. |

142| `cost.total_cost_usd` | The session cost, counted from when this Grok process opened or resumed the session, so a resumed session counts from the resume. Absent until the session incurs a cost, so treat absence as unknown rather than zero. |

143| `cost.total_duration_ms`, `.total_api_duration_ms` | Milliseconds since this Grok process opened or resumed the session, and milliseconds spent waiting on the API. |

144| `context_window.context_window_size` | The maximum context size, in tokens. |

145| `context_window.context_tokens` | The tokens the conversation currently occupies, counting input only. The value can fall after a compaction. |

146| `context_window.used_percentage`, `.remaining_percentage` | Current context window usage, as whole numbers from 0 to 100. |

147| `context_window.session_input_tokens`, `.session_output_tokens` | Token totals across the session so far, counted from when this process opened or resumed it. The values only grow. |

148| `context_window.session_usage.{input_tokens,output_tokens,cache_creation_input_tokens,cache_read_input_tokens}` | The session totals broken out. The input parts sum to `session_input_tokens`. |

149| `context_window.auto_compact_threshold_percent` | The usage percentage where the session auto-compacts. Absent when the agent does not report one. |

150| `effort.level` | The reasoning effort, when the model supports it. |

151| `turn.started_at_ms` | The Unix time, in milliseconds, when the current turn began. Absent between turns. |

152| `worktree.{name,path,branch,main_worktree_root}` | The active worktree, inside a linked worktree. `main_worktree_root` is the repository it branched from. |

153| `trigger` | Why this run happened: `refresh_interval` for a timed run, `state` otherwise. |

154 

155The JSON with every field present looks like this:

156 

157```json customLanguage="json"

158{

159 "schema_version": 1,

160 "cwd": "/home/user/project",

161 "session_id": "019fa651-6d59-7c83-a4f3-5a391e6901a1",

162 "session_name": "add status line",

163 "prompt_id": "97135ed2-71a5-4581-b959-3341bbd03e5f",

164 "transcript_path": "/home/user/sessions/019fa651/updates.jsonl",

165 "model": { "id": "grok-4.5", "display_name": "Grok 4.5" },

166 "workspace": {

167 "current_dir": "/home/user/project",

168 "repo_root": "/home/user/project",

169 "branch": "main",

170 "git_worktree": "feature-x",

171 "repo": { "host": "github.com", "owner": "owner", "name": "repo" }

172 },

173 "version": "0.2.112",

174 "cost": {

175 "total_cost_usd": 0.0123,

176 "total_duration_ms": 45000,

177 "total_api_duration_ms": 2300

178 },

179 "context_window": {

180 "context_window_size": 500000,

181 "context_tokens": 40000,

182 "session_input_tokens": 52000,

183 "session_output_tokens": 9500,

184 "session_usage": {

185 "input_tokens": 10000,

186 "output_tokens": 9500,

187 "cache_creation_input_tokens": 2000,

188 "cache_read_input_tokens": 40000

189 },

190 "used_percentage": 8,

191 "remaining_percentage": 92,

192 "auto_compact_threshold_percent": 80

193 },

194 "effort": { "level": "high" },

195 "turn": { "started_at_ms": 1730000000000 },

196 "worktree": {

197 "name": "feature-x",

198 "path": "/home/user/wt/feature-x",

199 "branch": "feature-x",

200 "main_worktree_root": "/home/user/project"

201 },

202 "trigger": "refresh_interval"

203}

204```

205 

206Grok omits a field it cannot determine rather than sending a placeholder value, so an absent field is never a zero. Handle absent fields in your script: `jq -r` prints the literal text `null` for a missing key, so write `// 0` or `// "?"` in jq, and use `?.` in JavaScript.

207 

208## Examples

209 

210### The current branch

211 

212The simplest status line is an inline command. Scripts run in the session's working directory, so this shows the checked-out branch:

213 

214```toml customLanguage="toml"

215[ui.status_line]

216type = "command"

217command = "git branch --show-current"

218```

219 

220### A session summary

221 

222The session data does not carry a count of changed files, so this script reads one from `git` and combines it with fields from the JSON:

223 

224```bash customLanguage="bash"

225#!/bin/sh

226payload=$(cat)

227dir=$(printf '%s' "$payload" | jq -r '.workspace.current_dir | split("/") | last')

228model=$(printf '%s' "$payload" | jq -r '.model.display_name // "?"')

229ctx=$(printf '%s' "$payload" | jq -r '.context_window.used_percentage // 0')

230branch=$(printf '%s' "$payload" | jq -r '.workspace.branch // "no branch"')

231changed=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')

232printf '%s │ %s │ %s%% ctx │ \033[32m%s\033[0m ±%s\n' "$dir" "$model" "$ctx" "$branch" "$changed"

233```

234 

235### An external status on a timer

236 

237With `refresh_interval` set, this script fetches the latest CI result on timed runs and reads a cached copy on state runs, so a busy turn does not become a stream of network requests:

238 

239```bash customLanguage="bash"

240#!/bin/sh

241payload=$(cat)

242trigger=$(printf '%s' "$payload" | jq -r '.trigger // "state"')

243session=$(printf '%s' "$payload" | jq -r '.session_id')

244cache="/tmp/statusline-ci-$session"

245 

246if [ "$trigger" = "refresh_interval" ] || [ ! -f "$cache" ]; then

247 # Only overwrite the cache when the fetch succeeds with output, so a

248 # failed or timed-out run keeps the last result instead of blanking it.

249 if result=$(gh run list --limit 1 --json status,conclusion \

250 --jq '.[0].conclusion // .[0].status' 2>/dev/null) && [ -n "$result" ]; then

251 printf '%s' "$result" > "$cache"

252 fi

253fi

254[ -f "$cache" ] && printf 'CI: %s\n' "$(cat "$cache")"

255```

256 

257## Tips

258 

259* Run your script by hand before configuring it, with sample JSON on standard input:

260 

261 ```bash customLanguage="bash"

262 ./statusline.sh <<'JSON'

263 {"workspace": {"current_dir": "/tmp/demo", "branch": "main"}, "model": {"display_name": "Grok 4.5"}}

264 JSON

265 ```

266 

267* A script that runs `git` on every update can lag in a large repository. Write the result to a file under `/tmp` named after `session_id`, and reuse it until it ages out. The identifier does not change during a session, and no two sessions share one, so concurrent sessions keep separate caches.

268 

269* Prefer `printf` over `echo -e`. Shells disagree about how `echo` treats escape sequences.

270 

271* Keep lines short. The row does not wrap, and a line longer than the row is cut.

272 

273## Troubleshooting

274 

275**Nothing shows.** Grok reads `[ui.status_line]` at startup, so restart it after editing `config.toml`. Check that `type` is not `disabled`, that a script is executable, and that it writes to standard output. The row renders once a session is active, in both the full-screen interface and minimal mode. It does not appear on the welcome screen or while a subagent view is open full screen.

276 

277**A message beginning `[ui.status_line]` fills the row.** Grok could not use the section as written. The message names the key it could not read, or what the chosen mode still needs. `grok inspect` lists the same problems. Fix the named key, or set `type = "disabled"` to remove the row and the message.

278 

279**A script error shows.** Anything your script prints is displayed even when it exits with a nonzero status. A script that prints nothing and fails shows `[status line: exit N]` until the next successful run. A script Grok cannot start, such as a file without the execute bit, shows `[status line: could not start the script: …]`, and one the system kills shows `[status line: killed by signal]`. Standard error is never displayed. Run Grok with `--debug` to read it.

280 

281**A setting in a repository has no effect.** Only your own `~/.grok/config.toml`, or configuration your administrator manages, can set a status line. A repository's local configuration cannot, because a `command` status line names a program your machine would run.

Details

214| `new_session_worktree_mode` | `[hints]` | `ask` | `always` | `never` (default `never`) | Whether `/new` offers a [worktree](/build/features/worktrees). |214| `new_session_worktree_mode` | `[hints]` | `ask` | `always` | `never` (default `never`) | Whether `/new` offers a [worktree](/build/features/worktrees). |

215| `fork_worktree_mode` | `[hints]` | `ask` | `always` | `never` (default `ask`) | Whether `/fork` offers a worktree. |215| `fork_worktree_mode` | `[hints]` | `ask` | `always` | `never` (default `ask`) | Whether `/fork` offers a worktree. |

216 216 

217### `[ui]`, `[ui.display_refresh]`, and `[ui.contextual_hints]`217### `[ui]`, `[ui.status_line]`, `[ui.display_refresh]`, and `[ui.contextual_hints]`

218 218 

219| Setting | Section | Values / default | Description |219| Setting | Section | Values / default | Description |

220| --- | --- | --- | --- |220| --- | --- | --- | --- |


251| `voice_keybind_enabled` | `[ui]` | `true` / `false` (default `true`) | Enable Ctrl+Space / F8 for voice dictation (`/voice` still works when off). |251| `voice_keybind_enabled` | `[ui]` | `true` / `false` (default `true`) | Enable Ctrl+Space / F8 for voice dictation (`/voice` still works when off). |

252| `voice_capture_mode` | `[ui]` | `hold` (default) | `toggle` | Hold-to-talk or press-to-toggle voice capture. |252| `voice_capture_mode` | `[ui]` | `hold` (default) | `toggle` | Hold-to-talk or press-to-toggle voice capture. |

253| `voice_stt_language` | `[ui]` | language code or `auto` (default `en` / `[voice].language`) | Speech-to-text language for dictation. |253| `voice_stt_language` | `[ui]` | language code or `auto` (default `en` / `[voice].language`) | Speech-to-text language for dictation. |

254| `status_line` | `[ui.status_line]` | `builtin` | `command` | `disabled` (default) | Live session values, or the output of your own script, in a row at the bottom. Sub-keys: `type`, `items`, `command`, `padding`, `refresh_interval`. See [Status line](/build/features/status-line). |

254| `auto_cadence_enabled` | `[ui.display_refresh]` | `true` / `false` (default `false`) | Match stream/scroll cadence to display refresh rate. Restart required. |255| `auto_cadence_enabled` | `[ui.display_refresh]` | `true` / `false` (default `false`) | Match stream/scroll cadence to display refresh rate. Restart required. |

255| `undo` | `[ui.contextual_hints]` | `true` / `false` (default `true`) | Ctrl+Z restores a wiped prompt draft. |256| `undo` | `[ui.contextual_hints]` | `true` / `false` (default `true`) | Ctrl+Z restores a wiped prompt draft. |

256| `plan_mode` | `[ui.contextual_hints]` | `true` / `false` (default `true`) | Suggest plan mode (Shift+Tab) for planning-style prompts. |257| `plan_mode` | `[ui.contextual_hints]` | `true` / `false` (default `true`) | Suggest plan mode (Shift+Tab) for planning-style prompts. |