claude-code - 💡(How to fix) Fix [BUG] Statusline not displaying on Windows [fixed]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

When configuring statusLine in ~/.claude/settings.json on Windows, the statusline bar at the bottom of Claude Code never appears. There is no error message, no stderr output, no indication in the UI that anything went wrong. The space where the statusline should be is simply empty.

Error Messages/Logs

In the Claude Code UI: No error message. No banner. No indication of failure. The statusline area is just blank. Inside the script when run by Claude Code (non-interactive Git Bash): The actual error, which I only discovered by adding echo "$input" 2>&1 > debug.log, is: This error never reaches the user. The script exits, output is empty, statusline area renders as nothing. When using the double-bash.exe wrapper config, the error is different but equally hidden: 5. Observe: No statusline appears. No error. 7. Observe: Each iteration saves without error. None of them produce a visible statusline. 6. Actual: Nothing visible. No error. 5. Actual: Nothing visible. No error. 6. To see the real error, add 2>>~/statusline-stderr.log somewhere. You'll find: cat: command not found and jq: command not found. 2. Validate end-to-end execution when /statusline is run interactively – execute the script with a sample payload and warn if exit code != 0 or output is empty

Root Cause

When configuring statusLine in ~/.claude/settings.json on Windows, the statusline bar at the bottom of Claude Code never appears. There is no error message, no stderr output, no indication in the UI that anything went wrong. The space where the statusline should be is simply empty. The configuration appears to save correctly when using the interactive /statusline command. Restarting Claude Code does not help. Running /statusline repeatedly to reconfigure does not help. The root cause turns out to be a combination of three issues stacked on top of each other, none of which surface any visible feedback in Claude Code:

Code Example

In the Claude Code UI: No error message. No banner. No indication of failure. The statusline area is just blank.

In claude --debug output: No mention of statusline failure. No exit code surfaced. No stderr captured visibly.

Inside the script when run manually with proper environment: Works correctly.

Inside the script when run by Claude Code (non-interactive Git Bash): The actual error, which I only discovered by adding echo "$input" 2>&1 > debug.log, is:

statusline-command.sh: line 3: cat: command not found

This error never reaches the user. The script exits, output is empty, statusline area renders as nothing.

When using the double-bash.exe wrapper config, the error is different but equally hidden:

bash.exe: warning: command substitution: ignored null byte in input
/c/Users/.../statusline-command.sh: command not found
Again – never surfaced in the Claude Code UI.

---

{
     "statusLine": {
       "type": "command",
       "command": "\"C:/Users/USERNAME/AppData/Local/Programs/Git/usr/bin/bash.exe\" C:/Users/USERNAME/.claude/statusline-command.sh"
     }
   }

---

#!/usr/bin/env bash
   echo "test"

---

{
     "statusLine": {
       "type": "command",
       "command": "~/.claude/statusline-command.sh"
     }
   }

---

#!/bin/sh
   export PATH="/c/Users/USERNAME/anaconda3/Library/mingw-w64/bin:$PATH"
   input=$(cat)
   echo "model: $(echo "$input" | jq -r '.model.display_name')"

---

winget install jqlang.jq

---

{
     "guid": "{00000000-0000-0000-0000-000000000001}",
     "name": "Git Bash",
     "commandline": "C:\\Program Files\\Git\\bin\\bash.exe -li",
     "icon": "C:\\Program Files\\Git\\mingw64\\share\\git\\git-for-windows.ico",
     "startingDirectory": "%USERPROFILE%",
     "hidden": false
   }

---

{
     "statusLine": {
       "type": "command",
       "command": "~/.claude/statusline-command.sh",
       "padding": 1
     }
   }

---

#!/usr/bin/env bash
export PATH="/usr/bin:/c/Users/YOUR_USERNAME/AppData/Local/Microsoft/WinGet/Packages/jqlang.jq_Microsoft.Winget.Source_8wekyb3d8bbwe:$PATH"
input=$(cat)

# ANSI 24-bit truecolor palette
CYAN='\033[38;2;0;212;232m'
GREEN='\033[38;2;0;232;162m'
YELLOW='\033[38;2;255;220;80m'
RED='\033[38;2;255;68;68m'
MUTED='\033[38;2;120;150;170m'
RESET='\033[0m'

model=$(echo "$input" | jq -r '.model.display_name // "unknown"')
ctx_used=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
five_h=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
week=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')

color_for() {
  val="$1"
  if [ -z "$val" ]; then echo "$MUTED"; return; fi
  if [ "$(printf '%.0f' "$val")" -ge 85 ]; then echo "$RED"
  elif [ "$(printf '%.0f' "$val")" -ge 60 ]; then echo "$YELLOW"
  else echo "$GREEN"
  fi
}

# Version check with cached background refresh
VERSION_CACHE="$HOME/.claude/.statusline-version-cache"
LATEST_CACHE="$HOME/.claude/.statusline-latest-cache"
CACHE_TTL_VERSION=3600
CACHE_TTL_LATEST=14400

file_age() {
  local f="$1"
  [ -f "$f" ] || { echo 999999; return; }
  local now=$(date +%s)
  local mtime=$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f" 2>/dev/null || echo 0)
  echo $((now - mtime))
}

current_ver=$(echo "$input" | jq -r '.version // .app.version // empty')

if [ -z "$current_ver" ]; then
  if [ -f "$VERSION_CACHE" ] && [ "$(file_age "$VERSION_CACHE")" -lt "$CACHE_TTL_VERSION" ]; then
    current_ver=$(cat "$VERSION_CACHE" 2>/dev/null)
  else
    (claude --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 > "$VERSION_CACHE.tmp" && mv "$VERSION_CACHE.tmp" "$VERSION_CACHE") &
    current_ver=$(cat "$VERSION_CACHE" 2>/dev/null)
  fi
fi

latest_ver=""
[ -f "$LATEST_CACHE" ] && latest_ver=$(cat "$LATEST_CACHE" 2>/dev/null)
if [ ! -f "$LATEST_CACHE" ] || [ "$(file_age "$LATEST_CACHE")" -gt "$CACHE_TTL_LATEST" ]; then
  (npm view @anthropic-ai/claude-code version 2>/dev/null > "$LATEST_CACHE.tmp" && mv "$LATEST_CACHE.tmp" "$LATEST_CACHE") &
fi

SEP="${MUTED}|${RESET}"
out="${CYAN}${model}${RESET}"

if [ -n "$ctx_used" ]; then
  pct=$(printf '%.0f' "$ctx_used")
  col=$(color_for "$ctx_used")
  out="${out} ${SEP} ${MUTED}ctx:${RESET}${col}${pct}%${RESET}"
fi

if [ -n "$five_h" ]; then
  pct=$(printf '%.0f' "$five_h")
  col=$(color_for "$five_h")
  out="${out} ${SEP} ${MUTED}5h:${RESET}${col}${pct}%${RESET}"
fi

if [ -n "$week" ]; then
  pct=$(printf '%.0f' "$week")
  col=$(color_for "$week")
  out="${out} ${SEP} ${MUTED}7d:${RESET}${col}${pct}%${RESET}"
fi

if [ -n "$current_ver" ]; then
  if [ -n "$latest_ver" ] && [ "$current_ver" != "$latest_ver" ]; then
    out="${out} ${SEP} ${YELLOW}v${current_ver} -> ${latest_ver} (update!)${RESET}"
  else
    out="${out} ${SEP} ${MUTED}v${current_ver}${RESET}"
  fi
fi

printf "%b\n" "$out"

---

Claude Opus 4.7 | ctx:13% | 5h:42% | 7d:18% | v2.1.153

---

Claude Opus 4.7 | ctx:13% | 5h:42% | 7d:18% | v2.1.153 -> 2.2.0 (update!)
RAW_BUFFERClick to expand / collapse

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

When configuring statusLine in ~/.claude/settings.json on Windows, the statusline bar at the bottom of Claude Code never appears. There is no error message, no stderr output, no indication in the UI that anything went wrong. The space where the statusline should be is simply empty. The configuration appears to save correctly when using the interactive /statusline command. Restarting Claude Code does not help. Running /statusline repeatedly to reconfigure does not help. The root cause turns out to be a combination of three issues stacked on top of each other, none of which surface any visible feedback in Claude Code:

  1. Running Claude Code from PowerShell / default Windows Terminal instead of Git Bash
  2. The official-looking config snippets online wrap the script in an explicit bash.exe call (double-bash invocation), which silently fails
  3. Most example scripts overwrite $PATH in a way that removes /usr/bin, which breaks cat, jq, and other core commands inside the non-interactive shell Claude Code spawns

What Should Happen?

What Should Happen After configuring statusLine in settings.json with a valid command and restarting Claude Code, the statusline should:

  • Appear at the bottom of the UI after the first user message and assistant response
  • Display the output of the configured command, formatted with ANSI escape sequences
  • Surface any execution errors (non-zero exit codes, stderr) to the user via the UI or --debug log
  • Validate the script end-to-end when the user runs /statusline interactively, not just save the config

Expected output for the working script:

Claude 3.5 Sonnet | ctx:42% | 5h:65% | 7d:88%

with truecolor ANSI per segment.

Error Messages/Logs

In the Claude Code UI: No error message. No banner. No indication of failure. The statusline area is just blank.

In claude --debug output: No mention of statusline failure. No exit code surfaced. No stderr captured visibly.

Inside the script when run manually with proper environment: Works correctly.

Inside the script when run by Claude Code (non-interactive Git Bash): The actual error, which I only discovered by adding echo "$input" 2>&1 > debug.log, is:

statusline-command.sh: line 3: cat: command not found

This error never reaches the user. The script exits, output is empty, statusline area renders as nothing.

When using the double-bash.exe wrapper config, the error is different but equally hidden:

bash.exe: warning: command substitution: ignored null byte in input
/c/Users/.../statusline-command.sh: command not found
Again – never surfaced in the Claude Code UI.

Steps to Reproduce

Steps to Reproduce

Initial broken state (what I had)

  1. Install Claude Code on Windows via npm: npm install -g @anthropic-ai/claude-code
  2. Launch Claude Code from PowerShell or default Windows Terminal profile (not Git Bash)
  3. Run /statusline inside Claude Code and follow the interactive setup
  4. Restart Claude Code
  5. Observe: No statusline appears. No error.
  6. Run /statusline again multiple times trying different configurations
  7. Observe: Each iteration saves without error. None of them produce a visible statusline.

Minimal reproduction of root cause #2 (double-bash)

  1. Add this to ~/.claude/settings.json:

    {
      "statusLine": {
        "type": "command",
        "command": "\"C:/Users/USERNAME/AppData/Local/Programs/Git/usr/bin/bash.exe\" C:/Users/USERNAME/.claude/statusline-command.sh"
      }
    }
  2. Create ~/.claude/statusline-command.sh:

    #!/usr/bin/env bash
    echo "test"
  3. chmod +x ~/.claude/statusline-command.sh

  4. Restart Claude Code

  5. Expected: Statusline shows test

  6. Actual: Nothing visible. No error.

Minimal reproduction of root cause #3 (PATH clobber)

  1. Fix the config to not double-bash:

    {
      "statusLine": {
        "type": "command",
        "command": "~/.claude/statusline-command.sh"
      }
    }
  2. Use a script that overwrites PATH (common pattern from Stack Overflow):

    #!/bin/sh
    export PATH="/c/Users/USERNAME/anaconda3/Library/mingw-w64/bin:$PATH"
    input=$(cat)
    echo "model: $(echo "$input" | jq -r '.model.display_name')"
  3. Restart Claude Code, send a message

  4. Expected: Statusline shows model name

  5. Actual: Nothing visible. No error.

  6. To see the real error, add 2>>~/statusline-stderr.log somewhere. You'll find: cat: command not found and jq: command not found.

Working configuration

  1. Install Git Bash (Git for Windows): https://git-scm.com/download/win
  2. Install jq:
    winget install jqlang.jq
  3. Add Git Bash profile to Windows Terminal (settings.json):
    {
      "guid": "{00000000-0000-0000-0000-000000000001}",
      "name": "Git Bash",
      "commandline": "C:\\Program Files\\Git\\bin\\bash.exe -li",
      "icon": "C:\\Program Files\\Git\\mingw64\\share\\git\\git-for-windows.ico",
      "startingDirectory": "%USERPROFILE%",
      "hidden": false
    }
  4. Set Git Bash as default profile.
  5. Configure ~/.claude/settings.json:
    {
      "statusLine": {
        "type": "command",
        "command": "~/.claude/statusline-command.sh",
        "padding": 1
      }
    }
  6. Create ~/.claude/statusline-command.sh (see full script below).
  7. chmod +x ~/.claude/statusline-command.sh.
  8. Launch Claude Code from inside Git Bash terminal, not PowerShell.
  9. Send any message.
  10. Result: Statusline appears with colored output.

Claude Model

Not sure / Multiple models

Is this a regression?

No, this never worked

Last Working Version

No response

Claude Code Version

2.1.153

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

Windows Terminal

Additional Information

How long this took to find

I spent days running the /statusline interactive setup repeatedly. Each iteration appeared to succeed – the config was saved, no errors were thrown, Claude Code restarted cleanly. The statusline simply never showed up. I tried at least ten different example scripts copied from Stack Overflow, blog posts, and GitHub issues. Every one of them used either the double-bash.exe wrapper or the PATH-overwrite pattern, both of which fail silently inside Claude Code's non-interactive Git Bash invocation.

Originally I wasn't even running Claude Code in Git Bash – I was using PowerShell and the default Windows Terminal profile. That added a third layer of failure. The fix only worked after I switched the terminal and corrected both script issues simultaneously.

Why this is hard to debug

  • Claude Code does not surface stderr from a failed statusline command in the UI
  • claude --debug does not include statusline execution details by default
  • The /statusline interactive setup writes the config but does not test end-to-end execution
  • Most online examples are written for interactive Git Bash sessions where the PATH issue doesn't manifest
  • The double-bash wrapper "works" often enough on simple ASCII paths to appear authoritative in tutorials
  • Windows PATH inheritance into non-interactive Git Bash is undocumented behavior most users don't think about

Working script (with version + update check)

#!/usr/bin/env bash
export PATH="/usr/bin:/c/Users/YOUR_USERNAME/AppData/Local/Microsoft/WinGet/Packages/jqlang.jq_Microsoft.Winget.Source_8wekyb3d8bbwe:$PATH"
input=$(cat)

# ANSI 24-bit truecolor palette
CYAN='\033[38;2;0;212;232m'
GREEN='\033[38;2;0;232;162m'
YELLOW='\033[38;2;255;220;80m'
RED='\033[38;2;255;68;68m'
MUTED='\033[38;2;120;150;170m'
RESET='\033[0m'

model=$(echo "$input" | jq -r '.model.display_name // "unknown"')
ctx_used=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
five_h=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
week=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')

color_for() {
  val="$1"
  if [ -z "$val" ]; then echo "$MUTED"; return; fi
  if [ "$(printf '%.0f' "$val")" -ge 85 ]; then echo "$RED"
  elif [ "$(printf '%.0f' "$val")" -ge 60 ]; then echo "$YELLOW"
  else echo "$GREEN"
  fi
}

# Version check with cached background refresh
VERSION_CACHE="$HOME/.claude/.statusline-version-cache"
LATEST_CACHE="$HOME/.claude/.statusline-latest-cache"
CACHE_TTL_VERSION=3600
CACHE_TTL_LATEST=14400

file_age() {
  local f="$1"
  [ -f "$f" ] || { echo 999999; return; }
  local now=$(date +%s)
  local mtime=$(stat -c %Y "$f" 2>/dev/null || stat -f %m "$f" 2>/dev/null || echo 0)
  echo $((now - mtime))
}

current_ver=$(echo "$input" | jq -r '.version // .app.version // empty')

if [ -z "$current_ver" ]; then
  if [ -f "$VERSION_CACHE" ] && [ "$(file_age "$VERSION_CACHE")" -lt "$CACHE_TTL_VERSION" ]; then
    current_ver=$(cat "$VERSION_CACHE" 2>/dev/null)
  else
    (claude --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 > "$VERSION_CACHE.tmp" && mv "$VERSION_CACHE.tmp" "$VERSION_CACHE") &
    current_ver=$(cat "$VERSION_CACHE" 2>/dev/null)
  fi
fi

latest_ver=""
[ -f "$LATEST_CACHE" ] && latest_ver=$(cat "$LATEST_CACHE" 2>/dev/null)
if [ ! -f "$LATEST_CACHE" ] || [ "$(file_age "$LATEST_CACHE")" -gt "$CACHE_TTL_LATEST" ]; then
  (npm view @anthropic-ai/claude-code version 2>/dev/null > "$LATEST_CACHE.tmp" && mv "$LATEST_CACHE.tmp" "$LATEST_CACHE") &
fi

SEP="${MUTED}|${RESET}"
out="${CYAN}${model}${RESET}"

if [ -n "$ctx_used" ]; then
  pct=$(printf '%.0f' "$ctx_used")
  col=$(color_for "$ctx_used")
  out="${out} ${SEP} ${MUTED}ctx:${RESET}${col}${pct}%${RESET}"
fi

if [ -n "$five_h" ]; then
  pct=$(printf '%.0f' "$five_h")
  col=$(color_for "$five_h")
  out="${out} ${SEP} ${MUTED}5h:${RESET}${col}${pct}%${RESET}"
fi

if [ -n "$week" ]; then
  pct=$(printf '%.0f' "$week")
  col=$(color_for "$week")
  out="${out} ${SEP} ${MUTED}7d:${RESET}${col}${pct}%${RESET}"
fi

if [ -n "$current_ver" ]; then
  if [ -n "$latest_ver" ] && [ "$current_ver" != "$latest_ver" ]; then
    out="${out} ${SEP} ${YELLOW}v${current_ver} -> ${latest_ver} (update!)${RESET}"
  else
    out="${out} ${SEP} ${MUTED}v${current_ver}${RESET}"
  fi
fi

printf "%b\n" "$out"

Output when up-to-date:

Claude Opus 4.7 | ctx:13% | 5h:42% | 7d:18% | v2.1.153

Output when update available (v... segment turns yellow):

Claude Opus 4.7 | ctx:13% | 5h:42% | 7d:18% | v2.1.153 -> 2.2.0 (update!)

Suggested improvements for Claude Code itself

  1. Surface stderr from statusline command execution to the user (UI banner, debug log, or stderr passthrough)
  2. Validate end-to-end execution when /statusline is run interactively – execute the script with a sample payload and warn if exit code != 0 or output is empty
  3. Document the non-interactive Git Bash invocation explicitly, including the PATH inheritance behavior on Windows
  4. Document the recommended terminal (Git Bash) for Windows users at the install step, not buried in the statusline docs

License

This bug report and the working script are MIT-licensed. Reuse freely.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING