A Custom Claude Code Status Line in PowerShell

How full is the context window right now? Which model is this session on, and which folder? Claude Code can show all of that in a line under the prompt, and the line is whatever a script of yours prints. It runs locally, costs no tokens, and on Windows it can be PowerShell.

This post covers the setting, a PowerShell script that shows model, folder, git branch, context used and cost, the JSON fields you can pick from, and the /statusline command that writes one for you. The script runs in Windows PowerShell 5.1 and PowerShell 7; the setting and the fields are from the current docs, linked at the end.

The Setting

A statusLine object in ~\.claude\settings.json with type set to command and the command to run. On Windows the command goes through Git Bash when it is installed and PowerShell when it is not, and either way, calling powershell explicitly works:

{
  "statusLine": {
    "type": "command",
    "command": "powershell -NoProfile -File C:/Users/<you>/.claude/statusline.ps1"
  }
}

Forward slashes in the path. Git Bash treats an unquoted backslash as an escape, so C:\Users\you\.claude\statusline.ps1 reaches the runner with its separators stripped and fails with no visible error. The docs are specific about this. ~/.claude/statusline.ps1 also works and expands to your Windows home directory. -NoProfile matters because the script runs on every update; a profile that loads modules would make it crawl. Claude Code reloads settings when you save, so the line appears without a restart.

The Script

Claude Code sends JSON on stdin and shows whatever the script prints. Save this as ~\.claude\statusline.ps1:

$s = [Console]::In.ReadToEnd() | ConvertFrom-Json
$dir = Split-Path $s.workspace.current_dir -Leaf
$pct = [int]($s.context_window.used_percentage)
$branch = git -C $s.workspace.current_dir branch --show-current 2>$null

$line = "[$($s.model.display_name)] $dir"
if ($branch) { $line += " ($branch)" }
$line += " | ctx $pct%"
if ($s.cost.total_cost_usd) { $line += (' | ${0:N2}' -f $s.cost.total_cost_usd) }
Write-Output $line

# [Opus] my-project (main) | ctx 43% | $1.23

used_percentage is null before the first response, and [int] turns that into 0 rather than an error. The git call is the one thing in the script that touches disk; 2>$null keeps it quiet outside a repository. Test it from a shell before you point the setting at it:

$sample = '{"model":{"display_name":"Opus"},"workspace":{"current_dir":"C:\\Users\\you\\my-project"},"context_window":{"used_percentage":42.6},"cost":{"total_cost_usd":1.2345}}'
$sample | powershell -NoProfile -File "$env:USERPROFILE\.claude\statusline.ps1"
# [Opus] my-project | ctx 43% | $1.23

The Fields You Can Show

The JSON on stdin has more than the script uses. The ones most people want:

  • model.display_name and model.id
  • workspace.current_dir, workspace.project_dir (where you launched), and workspace.git_worktree when you are in one
  • context_window.used_percentage, remaining_percentage, and context_window_size (200000, or 1000000 with extended context)
  • cost.total_cost_usd, cost.total_duration_ms, cost.total_lines_added and total_lines_removed
  • rate_limits.five_hour.used_percentage and rate_limits.seven_day.used_percentage, with a resets_at epoch for each, on a subscription
  • session_name when you named the session, version, effort.level, vim.mode
  • pr.number and pr.url when the branch has an open pull request

A rate limit line is the one worth adding on a Pro or Max plan. The fields are absent until the first response, so test before using them:

if ($s.rate_limits.five_hour) {
    $reset = [DateTimeOffset]::FromUnixTimeSeconds($s.rate_limits.five_hour.resets_at).LocalDateTime.ToString('HH:mm')
    $line += " | 5h $($s.rate_limits.five_hour.used_percentage)% (resets $reset)"
}

2 more rules from the docs: the script cannot read the terminal width with the usual calls, so use the COLUMNS environment variable Claude Code sets; and each line the script prints is a separate row, so a second Write-Output gives you a 2-line status.

When It Runs

Once at session start, then whenever a new assistant message arrives, /compact finishes, the permission mode changes, or you change the command in settings. Updates are debounced at 300 ms, and a script still running when the next update fires is cancelled. For something time-based, a clock or a rate limit countdown, add "refreshInterval": 30 to the setting to also re-run it every 30 seconds; the minimum is 1.

Colours work with ANSI escapes if the terminal supports them. In PowerShell the escape character is `e in 7 and [char]27 in 5.1, so "$([char]27)[32m$line$([char]27)[0m" prints the line in green in both.

Or Let /statusline Write It

Type /statusline in a session with a description and Claude Code writes a script into ~\.claude\ and updates your settings:

/statusline show model name, folder and context percentage with a progress bar
/statusline remove it

Approve the file edit prompts it asks for. It is the quickest route; the script above is for when you want to know what is in the file.

Other Considerations

It hides a few footer hints. With a status line configured, Claude Code stops showing most of the keyboard hints in the footer, including esc to interrupt. ? on an empty prompt still opens the shortcut panel.

It is hidden during some dialogs. Permission prompts and the help menu cover it temporarily. That is expected, not a broken script.

A script that fails leaves the line blank. Run the test line above outside Claude Code first; an exception in the script is invisible in the session.

The same JSON feeds hooks. transcript_path and session_id are in both, and Claude Code Hooks: Run a Check After Every Edit uses the same ReadToEnd | ConvertFrom-Json opening line.

Frequently Asked Questions

Why is my status line blank on Windows?

Almost always the path. Write it with forward slashes or ~/ in the command string, because Git Bash strips unquoted backslashes. Then run the script by hand with sample JSON to see any error.

Does the status line cost tokens?

No. It is a local script and the data comes from Claude Code, not the model. A heavy script costs you time on every update, which is why -NoProfile and a cheap git call are the whole budget here.

Can I show the 5-hour usage limit?

Yes, rate_limits.five_hour.used_percentage and resets_at are in the JSON on subscription plans, once the first response has arrived.

Can the status line have 2 lines?

Yes. Every line the script prints is a row. Keep it to 2; it sits above the footer and takes that space from the conversation.

How do I turn it off?

Delete the statusLine key from settings.json, or run /statusline remove it.


Related

If SQL Server is part of your day job, my current work lives over at sqldba.blog: production DBA scripts, an error library and the SSMS guide.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Looking for SQL Server?

This site started as a SQL Server blog in 2017 and the archive is still here. The new SQL Server writing, and all the scripts, moved to a site of their own.

sqldba.blogScripts, error library, wait types, SSMS
Browse by topic