Features

Status Line

The 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.

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

  • How full the context window is, and how close the session is to compacting
  • The branch and repository each session is working in
  • The session's running cost
  • A value from outside the session, such as a CI run, refreshed on a timer

The status line is off by default. The sections below describe each mode, how Grok runs your script, every field it sends, and a few scripts to start from.

Set up the status line

Add 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.

Because 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.

Show built-in items

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

TOML

[ui.status_line]
type = "builtin"
items = ["cwd", "model", "context"]   # default when omitted

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

ItemDescription
cwdThe name of the current directory.
modelThe model's display name.
contextContext 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.
costThe session cost. Hidden below $0.005.
turn-timerThe elapsed time of the current turn. Appears after one second.
session-nameThe session name, if set.

Run your own script

Set 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.

  1. Save a script, for example ~/.grok/statusline.sh, that reads JSON from standard input and prints a line. This example uses jq to extract two fields:

    Bash

    #!/bin/sh
    payload=$(cat)
    model=$(printf '%s' "$payload" | jq -r '.model.display_name // "?"')
    ctx=$(printf '%s' "$payload" | jq -r '.context_window.used_percentage // 0')
    printf '%s │ %s%% ctx\n' "$model" "$ctx"
    
  2. Make it executable with chmod +x ~/.grok/statusline.sh.

  3. Set it as the status line command:

    TOML

    [ui.status_line]
    type = "command"
    command = "~/.grok/statusline.sh"
    
  4. Restart Grok. The row appears once the session view is active.

Turn it off

Set 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.

Options

KeyTypeDefaultDescription
typestringdisabledbuiltin, command, or disabled.
itemsarray["cwd", "model", "context"]Built-in segments, in order.
commandstringnoneThe script or shell command for type = "command".
paddinginteger0Horizontal spacing, in characters per side, up to 16.
refresh_intervalintegerunsetSeconds 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.

How the status line works

Grok 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.

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

  • The session starting, or a client attaching to it
  • A turn ending
  • A model or reasoning effort switch
  • A commit or a branch switch
  • A compaction

Updates 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.

What your script can output.

  • 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.
  • ANSI colors are supported. Every other escape sequence, such as cursor movement, is removed.
  • OSC 8 hyperlinks are supported for http, https, and mailto targets. Other targets render as plain text.
  • Output past 64 KiB is truncated and the script is stopped.
  • A script that succeeds and prints nothing removes the row for that update rather than falling back to the built-in items.

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.

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.

Refresh on a timer

Set 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.

TOML

[ui.status_line]
type = "command"
command = "~/.grok/statusline.sh"
refresh_interval = 300   # seconds
  • 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".
  • 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.
  • 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.
  • 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.

Available data

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

FieldDescription
cwd, session_idThe working directory and the unique session identifier.
session_nameThe session's tab name, if set.
prompt_idThe UUID of the prompt being processed. Present only during a turn.
transcript_pathThe path to the session's updates.jsonl.
model.id, model.display_nameThe model identifier and display name.
workspace.current_dirThe current directory.
workspace.repo_rootThe repository root. Absent outside a repository.
workspace.branchThe checked-out branch. Absent on a detached HEAD.
workspace.git_worktreeThe worktree name, inside a linked worktree.
workspace.repo.{host,owner,name}The repository identity, parsed from the origin remote.
version, schema_versionThe Grok release, and the revision of this JSON shape. Test schema_version with >=.
cost.total_cost_usdThe 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.
cost.total_duration_ms, .total_api_duration_msMilliseconds since this Grok process opened or resumed the session, and milliseconds spent waiting on the API.
context_window.context_window_sizeThe maximum context size, in tokens.
context_window.context_tokensThe tokens the conversation currently occupies, counting input only. The value can fall after a compaction.
context_window.used_percentage, .remaining_percentageCurrent context window usage, as whole numbers from 0 to 100.
context_window.session_input_tokens, .session_output_tokensToken totals across the session so far, counted from when this process opened or resumed it. The values only grow.
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.
context_window.auto_compact_threshold_percentThe usage percentage where the session auto-compacts. Absent when the agent does not report one.
effort.levelThe reasoning effort, when the model supports it.
turn.started_at_msThe Unix time, in milliseconds, when the current turn began. Absent between turns.
worktree.{name,path,branch,main_worktree_root}The active worktree, inside a linked worktree. main_worktree_root is the repository it branched from.
triggerWhy this run happened: refresh_interval for a timed run, state otherwise.

The JSON with every field present looks like this:

JSON

{
  "schema_version": 1,
  "cwd": "/home/user/project",
  "session_id": "019fa651-6d59-7c83-a4f3-5a391e6901a1",
  "session_name": "add status line",
  "prompt_id": "97135ed2-71a5-4581-b959-3341bbd03e5f",
  "transcript_path": "/home/user/sessions/019fa651/updates.jsonl",
  "model": { "id": "grok-4.5", "display_name": "Grok 4.5" },
  "workspace": {
    "current_dir": "/home/user/project",
    "repo_root": "/home/user/project",
    "branch": "main",
    "git_worktree": "feature-x",
    "repo": { "host": "github.com", "owner": "owner", "name": "repo" }
  },
  "version": "0.2.112",
  "cost": {
    "total_cost_usd": 0.0123,
    "total_duration_ms": 45000,
    "total_api_duration_ms": 2300
  },
  "context_window": {
    "context_window_size": 500000,
    "context_tokens": 40000,
    "session_input_tokens": 52000,
    "session_output_tokens": 9500,
    "session_usage": {
      "input_tokens": 10000,
      "output_tokens": 9500,
      "cache_creation_input_tokens": 2000,
      "cache_read_input_tokens": 40000
    },
    "used_percentage": 8,
    "remaining_percentage": 92,
    "auto_compact_threshold_percent": 80
  },
  "effort": { "level": "high" },
  "turn": { "started_at_ms": 1730000000000 },
  "worktree": {
    "name": "feature-x",
    "path": "/home/user/wt/feature-x",
    "branch": "feature-x",
    "main_worktree_root": "/home/user/project"
  },
  "trigger": "refresh_interval"
}

Grok 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.

Examples

The current branch

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

TOML

[ui.status_line]
type = "command"
command = "git branch --show-current"

A session summary

The 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:

Bash

#!/bin/sh
payload=$(cat)
dir=$(printf '%s' "$payload" | jq -r '.workspace.current_dir | split("/") | last')
model=$(printf '%s' "$payload" | jq -r '.model.display_name // "?"')
ctx=$(printf '%s' "$payload" | jq -r '.context_window.used_percentage // 0')
branch=$(printf '%s' "$payload" | jq -r '.workspace.branch // "no branch"')
changed=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
printf '%s │ %s │ %s%% ctx │ \033[32m%s\033[0m ±%s\n' "$dir" "$model" "$ctx" "$branch" "$changed"

An external status on a timer

With 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:

Bash

#!/bin/sh
payload=$(cat)
trigger=$(printf '%s' "$payload" | jq -r '.trigger // "state"')
session=$(printf '%s' "$payload" | jq -r '.session_id')
cache="/tmp/statusline-ci-$session"

if [ "$trigger" = "refresh_interval" ] || [ ! -f "$cache" ]; then
  # Only overwrite the cache when the fetch succeeds with output, so a
  # failed or timed-out run keeps the last result instead of blanking it.
  if result=$(gh run list --limit 1 --json status,conclusion \
      --jq '.[0].conclusion // .[0].status' 2>/dev/null) && [ -n "$result" ]; then
    printf '%s' "$result" > "$cache"
  fi
fi
[ -f "$cache" ] && printf 'CI: %s\n' "$(cat "$cache")"

Tips

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

    Bash

    ./statusline.sh <<'JSON'
    {"workspace": {"current_dir": "/tmp/demo", "branch": "main"}, "model": {"display_name": "Grok 4.5"}}
    JSON
    
  • 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.

  • Prefer printf over echo -e. Shells disagree about how echo treats escape sequences.

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

Troubleshooting

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.

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.

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.

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.


Last updated: September 3, 2026