SpyBara
Go Premium

Documentation 2026-09-03 16:59 UTC to 2026-09-07 16:01 UTC

2 files changed +35 −224. 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
Details

2 2 

3# Status Line3# Status Line

4 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.5The status line is an optional row that shows live session values. It can show the model, context-window usage, cost, or the output of a script you provide.

6 6 

7Use a status line to watch values that change as you work:7In the full-screen view the row sits above the shortcuts bar. In minimal mode it sits under the prompt info row. The row is hidden on the welcome screen and while a fullscreen subagent view is open. The status line is off by default.

8 8 

9* How full the context window is, and how close the session is to compacting9## Configure the status line

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 10 

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).11Add a `[ui.status_line]` section to `~/.grok/config.toml`. Restart Grok.

15 12 

16## Set up the status line13The `type` key selects the mode. `builtin` shows values Grok renders. `command` runs your script. `disabled` shows nothing.

17 14 

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.15Grok reads this section only from your own configuration, or from configuration your administrator manages. A cloned repository cannot set a status line.

19 16 

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.17### Built-in items

21 18 

22### Show built-in items19Items appear in the order you list them. Long values shorten with an ellipsis.

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 20 

26```toml customLanguage="toml"21```toml customLanguage="toml"

27[ui.status_line]22[ui.status_line]


35| --- | --- |30| --- | --- |

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

37| `model` | The model's display name. |32| `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. |33| `context` | Context-window usage, as a percentage. The value turns amber at the auto-compaction threshold, or at 80 percent when the agent reports none. |

39| `cost` | The session cost. Hidden below $0.005. |34| `cost` | Cost for this Grok process. Hidden below $0.005. A resumed session counts from the resume. |

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

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

42 37 

43### Run your own script38### A command script

39 

40Set `type` to `command`. Point `command` at a script path or an inline shell command. A leading `~/` expands to your home directory.

44 41 

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.42These recipes are POSIX shell. They are tested on macOS and Linux. A `command` status line is untested on Windows.

46 43 

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:441. Save `~/.grok/statusline.sh`. The script reads JSON from standard input and prints a line. This example uses [`jq`](https://jqlang.org/):

48 45 

49 ```bash customLanguage="bash"46 ```bash customLanguage="bash"

50 #!/bin/sh47 #!/bin/sh


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

55 ```52 ```

56 53 

572. Make it executable with `chmod +x ~/.grok/statusline.sh`.542. Run `chmod +x ~/.grok/statusline.sh`. A file without the execute bit shows `[status line: could not start the script: …]`.

58 55 

593. Set it as the status line command:563. Set the command:

60 57 

61 ```toml customLanguage="toml"58 ```toml customLanguage="toml"

62 [ui.status_line]59 [ui.status_line]


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

65 ```62 ```

66 63 

674. Restart Grok. The row appears once the session view is active.644. Restart Grok. The row appears once a session 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 65 

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.66The script receives one JSON object on standard input. Common fields are `model.display_name`, `context_window.used_percentage`, `workspace.branch`, and `cwd`. Grok omits a field it cannot determine. Guard missing keys (`// "?"` in jq).

106 67 

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.68An idle session does not re-run the script. Set `refresh_interval` (seconds) on a `command` row to also run it on a timer. The key does nothing under `builtin` or `disabled`. `grok inspect` still reports it there.

108 69 

109## Refresh on a timer70Test the script before you configure it:

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 71 

239```bash customLanguage="bash"72```bash customLanguage="bash"

240#!/bin/sh73~/.grok/statusline.sh <<'JSON'

241payload=$(cat)74{"workspace": {"current_dir": "/tmp/demo", "branch": "main"}, "model": {"display_name": "Grok 4.5"}}

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

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```76```

256 77 

257## Tips78### Disable the status line

258 

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

260 79 

261 ```bash customLanguage="bash"80Set `type` to `disabled`. `off`, `none`, and `hidden` mean the same. Removing the `[ui.status_line]` section also disables the row.

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 81 

273## Troubleshooting82## Troubleshooting

274 83 

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.84Grok reads `[ui.status_line]` at startup. Restart Grok after you edit `config.toml`.

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 85 

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.86`grok inspect` lists problems in the section. A row that begins with `[ui.status_line]` names the key Grok could not read.

280 87 

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.88A script that prints nothing and fails shows `[status line: exit N]`. A timeout shows `[status line: timed out]`. A kill shows `[status line: killed by signal]`. A spawn failure, including a missing execute bit, shows `[status line: could not start the script: …]`. Standard error is never shown. Run Grok with `--debug` to read it.

overview.md +5 −1

Details

125console.log(text);125console.log(text);

126```126```

127 127 

128## Next128## Features

129 129 

130* [Status Line](/build/features/status-line). Live session context in a row at the bottom of the TUI.

130* [Skills, Plugins & Marketplaces](/build/features/skills-plugins-marketplaces)131* [Skills, Plugins & Marketplaces](/build/features/skills-plugins-marketplaces)

131* [Modes and Commands](/build/modes-and-commands)132* [Modes and Commands](/build/modes-and-commands)

133 

134## Next

135 

132* [Headless & Scripting](/build/cli/headless-scripting)136* [Headless & Scripting](/build/cli/headless-scripting)

133* [Enterprise Deployments](/build/enterprise)137* [Enterprise Deployments](/build/enterprise)

134* [Grok Bot](/grok-bot/overview) — AI teammates on a cloud computer138* [Grok Bot](/grok-bot/overview) — AI teammates on a cloud computer