<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Siddaram Metri]]></title><description><![CDATA[Siddaram Metri]]></description><link>https://siddarammetri.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 28 Oct 2026 12:04:47 GMT</lastBuildDate><atom:link href="https://siddarammetri.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Claude Code Status Line: Live Usage, Context & Reset Timers]]></title><description><![CDATA[Show context usage, 5-hour and weekly limits, reset countdowns and session cost in your Claude Code status line, with a safe install guide.]]></description><link>https://siddarammetri.hashnode.dev/claude-code-custom-status-line</link><guid isPermaLink="true">https://siddarammetri.hashnode.dev/claude-code-custom-status-line</guid><category><![CDATA[claude-code]]></category><category><![CDATA[terminal]]></category><category><![CDATA[macOS]]></category><category><![CDATA[Bash]]></category><category><![CDATA[Developer Tools]]></category><dc:creator><![CDATA[Siddaram Metri]]></dc:creator><pubDate>Mon, 19 Oct 2026 10:04:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/678212dc41a3b15ad5b27b3b/cae8fb3f-2bec-4c69-9696-966b38a3d6db.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When I work in Claude Code, I keep asking the same questions: how full is this conversation, how much of my 5-hour and weekly limit have I used, and when does it reset? Built-in commands answer each one, but only when I stop and run them. I wanted the answers on screen all the time, next to the model, the git branch and the session's estimated cost.</p>
<p>Claude Code supports this through its <strong>status line</strong>, a row at the bottom of the terminal that shows whatever a script of yours prints. This post covers the status line I use every day: what it shows, how to install it safely, how it works, the design mistakes I made building it, and how to remove it again.</p>
<img src="https://siddarammetri.in/blog/claude-code-status-line/statusline-anatomy.svg" alt="The status line: live dot and branch, session ID, model, context bar, 5-hour bar with reset countdown, weekly bar and estimated session cost" style="display:block;margin:0 auto" />

<p><em>The finished status line. In the terminal it's one row; it's split in two here so the labels fit.</em></p>
<h2>Shortcut: let Claude Code do it for you</h2>
<blockquote>
<p>Use <a href="https://www.anthropic.com/claude-code">Claude Code</a>? Open the <a href="https://siddarammetri.in/blogs/claude-code-custom-status-line">original post</a> and click <strong>Copy Claude Code prompt</strong>. Paste it into Claude Code and it does the setup for you: it downloads the installer, tells you what it will change and asks before running it.</p>
<p>Not using Claude Code? Follow the steps below by hand.</p>
</blockquote>
<h2>What the status line shows</h2>
<pre><code class="language-text">● main │ Session: 1fb4ade2 │ Opus 5.5 │ ctx ■■■□□□□□  32% │ 5h ■□□□□□□□   4% ↻03:20 │ wk ■■■■■□□□  65% │ Est. usage: $6.44
</code></pre>
<table>
<thead>
<tr>
<th>Segment</th>
<th>What it is</th>
<th>Where it comes from</th>
</tr>
</thead>
<tbody><tr>
<td><code>● main</code></td>
<td>Git branch, or the folder name outside a repo</td>
<td>Read locally with <code>git</code> on every run</td>
</tr>
<tr>
<td><code>Session: 1fb4ade2</code></td>
<td>First 8 characters of the session ID</td>
<td><code>session_id</code></td>
</tr>
<tr>
<td><code>Opus 5.5</code></td>
<td>The current model</td>
<td><code>model.display_name</code></td>
</tr>
<tr>
<td><code>ctx</code> bar</td>
<td>How full the conversation's context window is</td>
<td><code>context_window.used_percentage</code></td>
</tr>
<tr>
<td><code>5h</code> bar + <code>↻03:20</code></td>
<td>Use of your 5-hour limit, and the time until it resets</td>
<td><code>rate_limits.five_hour</code></td>
</tr>
<tr>
<td><code>wk</code> bar</td>
<td>Use of your weekly (7-day) limit</td>
<td><code>rate_limits.seven_day</code></td>
</tr>
<tr>
<td><code>Est. usage</code></td>
<td>Estimated cost of this session in US dollars</td>
<td><code>cost.total_cost_usd</code></td>
</tr>
</tbody></table>
<p>Each bar is 8 squares; the filled part is coloured and the rest stays dark grey (shown as <code>□</code> above).</p>
<p>Not every value is equally fresh:</p>
<ul>
<li><p><strong>Live, worked out on your machine:</strong> the git branch, the <code>↻</code> countdown (your clock compared with the reset time) and the animation.</p>
</li>
<li><p><strong>Updated when Claude Code gets a new response:</strong> the <code>ctx</code>, <code>5h</code> and <code>wk</code> percentages and the cost. The script redraws every second, but these numbers only change when Claude Code itself receives new data.</p>
</li>
<li><p><strong>An estimate:</strong> <code>Est. usage</code> is computed by Claude Code at list price and resets to $0 when <code>/clear</code> starts a new session. It <a href="https://code.claude.com/docs/en/statusline">may differ from your actual bill</a>, and on a Pro or Max plan it isn't a charge at all.</p>
</li>
<li><p><strong>Sometimes unavailable:</strong> if a field is missing, its segment is simply left out. Nothing is guessed.</p>
</li>
</ul>
<h2>Prerequisites and compatibility</h2>
<ul>
<li><p><strong>Claude Code</strong> with status line support. I built and tested this with Claude Code 2.1.292 on macOS (Apple Terminal).</p>
</li>
<li><p><strong>bash</strong>, plus <code>jq</code> <strong>or</strong> <code>python3</code> (the installer uses one of them to edit your settings, and the script uses one to read Claude Code's data). Check with <code>which jq python3</code>.</p>
</li>
<li><p><strong>git</strong> (optional) for the branch segment.</p>
</li>
<li><p><strong>A terminal with 256-colour support.</strong> Almost every modern terminal has it.</p>
</li>
<li><p><strong>For the</strong> <code>5h</code> <strong>and</strong> <code>wk</code> <strong>bars: a Claude.ai Pro or Max subscription.</strong> Claude Code only sends the 5-hour and weekly limits to Pro and Max subscribers, and only <a href="https://code.claude.com/docs/en/statusline">after the first API response in a session</a>. With an API key, Amazon Bedrock or Google Vertex, those two segments don't appear; the rest still works.</p>
</li>
</ul>
<p>It's written for macOS and Linux. I've only tested it on macOS. On Windows, Claude Code runs status line commands <a href="https://code.claude.com/docs/en/statusline">through Git Bash or PowerShell</a>; this bash script may work under Git Bash, but I haven't tested it.</p>
<h3>Optional: make the terminal nicer for prompts</h3>
<p>This is separate from the status line, but it's the first thing I set up. Run this inside Claude Code:</p>
<pre><code class="language-text">/terminal-setup
</code></pre>
<p>What it does <a href="https://code.claude.com/docs/en/terminal-config">depends on your terminal</a>:</p>
<table>
<thead>
<tr>
<th>Terminal</th>
<th>What <code>/terminal-setup</code> changes</th>
</tr>
</thead>
<tbody><tr>
<td>Apple Terminal (macOS)</td>
<td>Turns on <strong>"Use Option as Meta Key"</strong> and turns off the <strong>audible bell</strong> (the bell is left on in screen reader mode)</td>
</tr>
<tr>
<td>iTerm2</td>
<td>Lets terminal apps access the clipboard, so <code>/copy</code> works (restart iTerm2 afterwards)</td>
</tr>
<tr>
<td>VS Code, Cursor, Devin Desktop, Alacritty before 0.16, Zed</td>
<td>Adds a <strong>Shift+Enter</strong> key binding for new lines. In VS Code, Cursor and Devin Desktop it also turns off the integrated terminal's GPU acceleration and adjusts scroll sensitivity</td>
</tr>
</tbody></table>
<p>If you accepted Claude Code's terminal setup prompt on first run, this has already been done. With Option as Meta, these shortcuts work:</p>
<img src="https://siddarammetri.in/blog/claude-code-status-line/terminal-keys.svg" alt="Option+Enter adds a second line to the prompt; Option+Left jumps the cursor back one word at a time" style="display:block;margin:0 auto" />

<p><em>Option+Enter for a new line, Option+Arrow to move by word.</em></p>
<table>
<thead>
<tr>
<th>Keys</th>
<th>Action</th>
</tr>
</thead>
<tbody><tr>
<td><code>⌥ + Enter</code></td>
<td>New line inside your message</td>
</tr>
<tr>
<td><code>⌥ + ←</code> / <code>⌥ + →</code></td>
<td>Jump back / forward one word</td>
</tr>
<tr>
<td><code>⌥ + Delete</code></td>
<td>Delete the previous word</td>
</tr>
<tr>
<td><code>⌥ + D</code></td>
<td>Delete the next word</td>
</tr>
</tbody></table>
<p>In any terminal, <strong>Ctrl+J</strong> or typing <code>\</code> then <strong>Enter</strong> also adds a new line, and <strong>Shift+Enter</strong> already works in Apple Terminal, iTerm2 and several others without setup.</p>
<p><strong>Trade-off:</strong> with Option as Meta, Option no longer types characters like <code>©</code>, <code>€</code> or <code>ñ</code> in Terminal. To undo it, untick <strong>Terminal → Settings → Profiles → Keyboard → "Use Option as Meta Key"</strong>.</p>
<h2>Install</h2>
<p>The installer is a single bash file. Download it, read it, then run it:</p>
<pre><code class="language-bash">curl -fsSL https://siddarammetri.in/blog/claude-code-status-line/install.sh \
  -o /tmp/statusline-install.sh
less /tmp/statusline-install.sh   # read it first (q to quit)
bash /tmp/statusline-install.sh
</code></pre>
<blockquote>
<p><strong>Read scripts before you run them.</strong> Don't pipe <code>curl</code> straight into <code>bash</code>, for this installer or any other. The file is short, and the top of it lists exactly what it changes.</p>
</blockquote>
<p>It makes exactly these changes, and nothing else:</p>
<table>
<thead>
<tr>
<th>File</th>
<th>Change</th>
<th>Backup</th>
</tr>
</thead>
<tbody><tr>
<td><code>~/.claude/statusline-command.sh</code></td>
<td>Written (replaced if it already exists) and made executable</td>
<td>An existing file is copied to <code>statusline-command.sh.bak-&lt;date-time&gt;</code> first</td>
</tr>
<tr>
<td><code>~/.claude/settings.json</code></td>
<td>Created if missing. Its <code>statusLine</code> entry is set to run the script every second; a <code>statusLine</code> you already had is <strong>replaced</strong>. Your other settings are kept</td>
<td>The file is always copied to <code>settings.json.bak-&lt;date-time&gt;</code> first</td>
</tr>
</tbody></table>
<p>It doesn't touch your terminal, fonts or shell configuration.</p>
<p>A successful run looks like this (in your terminal the bars are coloured):</p>
<pre><code class="language-text">✓ Wrote /Users/you/.claude/statusline-command.sh
✓ Updated /Users/you/.claude/settings.json (backup: /Users/you/.claude/settings.json.bak-20261019-031500)

Preview with sample data:
● main │ Session: 1fb4ade2 │ Opus 5.5 │ ctx ■■■■■■■■  32% │ 5h ■■■■■■■■   4% ↻03:20 │ wk ■■■■■■■■  65% │ Est. usage: $6.44

Done. It appears at the bottom of Claude Code within a second (bars show after the first reply).
Undo: restore /Users/you/.claude/settings.json.bak-20261019-031500
</code></pre>
<p>The preview uses sample numbers, not your real usage.</p>
<h3>Check that it worked</h3>
<ol>
<li><p>Confirm the setting is in place:</p>
<pre><code class="language-bash">jq .statusLine ~/.claude/settings.json
</code></pre>
<p>You should see <code>"type": "command"</code>, <code>"command": "bash /Users/you/.claude/statusline-command.sh"</code> and <code>"refreshInterval": 1</code>.</p>
</li>
<li><p>Start Claude Code (<code>claude</code>) and send any message. The branch, session and model appear straight away; the <code>ctx</code>, <code>5h</code>, <code>wk</code> and cost segments appear after the first reply.</p>
</li>
</ol>
<h3>If the install fails</h3>
<table>
<thead>
<tr>
<th>Message or symptom</th>
<th>What it means</th>
<th>Fix</th>
</tr>
</thead>
<tbody><tr>
<td><code>Couldn't find jq or python3</code></td>
<td>Neither is installed. The script was written, but no <code>statusLine</code> was added (a backup copy of <code>settings.json</code> was still made). The status line script also needs one of them to read Claude Code's data</td>
<td>Install one (<code>brew install jq</code> on macOS) and run the installer again</td>
</tr>
<tr>
<td>A jq <code>parse error</code> or a Python <code>JSONDecodeError</code></td>
<td>Your <code>settings.json</code> isn't valid JSON, so it was left unchanged</td>
<td>Fix the file (<code>jq . ~/.claude/settings.json</code> shows where), then run the installer again</td>
</tr>
<tr>
<td><code>curl: (22) … 404</code></td>
<td>The download URL is wrong</td>
<td>Copy the command again; the file lives under <code>/blog/</code>, not <code>/blogs/</code></td>
</tr>
</tbody></table>
<h2>Configure and customize</h2>
<p>The installer adds this to <code>~/.claude/settings.json</code>:</p>
<pre><code class="language-json">{
  "statusLine": {
    "type": "command",
    "command": "bash /Users/you/.claude/statusline-command.sh",
    "refreshInterval": 1
  }
}
</code></pre>
<p><code>refreshInterval</code> re-runs the script every N seconds in addition to Claude Code's normal updates. One second keeps the countdown and animation moving. On my Mac one run takes about 0.1 seconds; raise the number if you'd rather it ran less often, or remove the line to update only on events.</p>
<p>Everything else lives in <code>~/.claude/statusline-command.sh</code>:</p>
<table>
<thead>
<tr>
<th>To change</th>
<th>Edit</th>
</tr>
</thead>
<tbody><tr>
<td>Bar length</td>
<td><code>BAR_WIDTH=8</code></td>
</tr>
<tr>
<td>Colour thresholds</td>
<td>the <code>level()</code> function (50 / 80 / 90 / 95%)</td>
</tr>
<tr>
<td>Colours</td>
<td>the 256-colour numbers in <code>level()</code> and <code>glow()</code></td>
</tr>
<tr>
<td>Bar character</td>
<td>the <code>■</code> in the <code>bar()</code> function (<code>▬</code> and <code>▄</code> also work)</td>
</tr>
<tr>
<td>Which segments show</td>
<td>the lines that build <code>out+=</code> near the bottom; delete one to hide it</td>
</tr>
</tbody></table>
<p>Your changes show up the next time the script runs. The installer overwrites the script, so re-running it replaces your edits (the previous version is backed up first).</p>
<h2>How it works</h2>
<img src="https://siddarammetri.in/blog/claude-code-status-line/how-it-works.svg" alt="Claude Code sends JSON on stdin to your script, which prints text on stdout that appears in the status bar" style="display:block;margin:0 auto" />

<p><em>Claude Code supplies the data; the script decides how it looks.</em></p>
<p>The status line is an <a href="https://code.claude.com/docs/en/statusline">official Claude Code feature</a>:</p>
<ol>
<li><p>Claude Code runs your command and passes the session's details to it as <strong>JSON on stdin</strong>.</p>
</li>
<li><p>Your script prints one line of text (more lines are allowed; ANSI colours are fine).</p>
</li>
<li><p>Claude Code shows that output at the bottom of the terminal.</p>
</li>
</ol>
<p>It <a href="https://code.claude.com/docs/en/statusline">runs locally and doesn't use API tokens</a>. Claude Code re-runs it when a session starts, when a new assistant message arrives, after <code>/compact</code>, when the permission mode changes, when a <code>refreshInterval</code> timer elapses, and when a usage window reaches its reset time. Updates are batched over about 300 ms.</p>
<p>The fields this script reads look like this (trimmed; Claude Code sends more):</p>
<pre><code class="language-json">{
  "session_id": "1fb4ade2-…",
  "model": { "display_name": "Opus 5.5" },
  "workspace": { "current_dir": "/Users/you/project" },
  "cost": { "total_cost_usd": 6.44 },
  "context_window": { "used_percentage": 32 },
  "rate_limits": {
    "five_hour": { "used_percentage": 4, "resets_at": 1792371600 },
    "seven_day": { "used_percentage": 65, "resets_at": 1792890000 }
  }
}
</code></pre>
<p><code>resets_at</code> is a Unix timestamp in seconds. A few fields come and go, and the script handles each one:</p>
<ul>
<li><p>The 5-hour and weekly <code>rate_limits</code> are only sent to Pro and Max subscribers, only after the first response, and each window can be missing on its own. Claude Code drops a window once its reset time passes, so a bar can briefly disappear right after a reset.</p>
</li>
<li><p><code>context_window.used_percentage</code> can be empty early in a session.</p>
</li>
<li><p>The script reads every field with a fallback, so a missing field means a missing segment rather than an error.</p>
</li>
</ul>
<h2>Design decisions (and mistakes)</h2>
<h3>Colours that mean something</h3>
<p>One colour for everything tells you nothing. Each bar changes colour at four points:</p>
<img src="https://siddarammetri.in/blog/claude-code-status-line/color-levels.svg" alt="Bars at 25, 55, 85, 92 and 97 percent in green, yellow, orange, red and blinking bright red" style="display:block;margin:0 auto" />

<p><em>Each bar is coloured on its own, so a green context bar can sit next to a yellow weekly bar.</em></p>
<table>
<thead>
<tr>
<th>Usage</th>
<th>Colour</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td>below 50%</td>
<td>green</td>
<td>Plenty left</td>
</tr>
<tr>
<td>50–79%</td>
<td>yellow</td>
<td>Halfway</td>
</tr>
<tr>
<td>80–89%</td>
<td>orange</td>
<td>Slow down</td>
</tr>
<tr>
<td>90–94%</td>
<td>red</td>
<td>Nearly out</td>
</tr>
<tr>
<td>95%+</td>
<td>bright red, blinking</td>
<td>About to hit the limit</td>
</tr>
</tbody></table>
<h3>The bar character matters more than you'd think</h3>
<p>My first version used full blocks (<code>█</code>), which were far too chunky. The thin line <code>━</code> disappeared. Then I tried the lower ¾ block <code>▆</code>, and it looked <em>wrong</em> without me knowing why: the bars seemed to sit lower than the text.</p>
<p>That's exactly what was happening. Block characters are anchored to the <strong>bottom of the character cell</strong>, so <code>▆</code> hangs below the baseline. A square <code>■</code> is <strong>centred</strong> in the cell and lines up with the letters.</p>
<img src="https://siddarammetri.in/blog/claude-code-status-line/alignment.svg" alt="The lower block hangs below the text baseline; the square is centred and lines up" style="display:block;margin:0 auto" />

<p><em>Same font, same size: the block sits low, the square lines up with the text.</em></p>
<p>I found this by rendering each candidate next to text in the same font and comparing them, rather than guessing. And one rule I'd keep: <strong>don't change your terminal font to make a status line look right.</strong> Change the characters instead.</p>
<h3>Numbers that don't make the line jump</h3>
<p><code>4%</code> is narrower than <code>19%</code>, so everything after it shifted sideways whenever a value crossed 10%. Padding every number to three characters (<code>printf '%3s'</code>) keeps every separator in a fixed place.</p>
<h3>Animation with one frame per second</h3>
<p>A shell script can't animate on its own, but with <code>refreshInterval: 1</code> each run is a frame. The script uses the current second (<code>date +%s</code>) to move a lighter "shine" square along each bar, pulse the leading square so even a one-square bar visibly moves, blink the whole bar from 95%, and pulse the <code>●</code> dot.</p>
<h2>Troubleshooting and limitations</h2>
<table>
<thead>
<tr>
<th>Problem</th>
<th>Fix</th>
</tr>
</thead>
<tbody><tr>
<td>Nothing appears</td>
<td>Run <code>jq .statusLine ~/.claude/settings.json</code> to check the setting, then test the script by hand: `echo '{"model":{"display_name":"Test"}}'</td>
</tr>
<tr>
<td>Only branch, session and model show</td>
<td>Normal before the first reply; the other values arrive with it</td>
</tr>
<tr>
<td><code>5h</code> / <code>wk</code> never appear</td>
<td>You're not on a Pro or Max plan in this session (for example an API key, Bedrock or Vertex), so Claude Code sends no plan-limit data</td>
</tr>
<tr>
<td>A bar vanishes right after a reset</td>
<td>Claude Code drops the expired window; it comes back with the next reply</td>
</tr>
<tr>
<td>Usage looks out of date</td>
<td>The numbers update with Claude Code's responses. If you've used Claude elsewhere, send a message here or run <code>/usage</code></td>
</tr>
<tr>
<td>Squares look misaligned or gappy</td>
<td>Font-dependent. Swap <code>■</code> for <code>▬</code> or <code>▄</code> in <code>bar()</code>, and keep your font</td>
</tr>
<tr>
<td>Line too long for the window</td>
<td>Lower <code>BAR_WIDTH</code>, or remove the <code>Session:</code> segment</td>
</tr>
</tbody></table>
<p>Claude Code's built-in commands are still useful for anything the line doesn't show:</p>
<table>
<thead>
<tr>
<th>Command</th>
<th>Shows</th>
</tr>
</thead>
<tbody><tr>
<td><code>/usage</code></td>
<td>Session cost, plan usage limits and activity stats (<code>/cost</code> and <code>/stats</code> are aliases)</td>
</tr>
<tr>
<td><code>/context</code></td>
<td>A coloured grid of what's using the context window</td>
</tr>
<tr>
<td><code>/status</code></td>
<td>Version, model, account and connectivity</td>
</tr>
<tr>
<td><code>/statusline</code></td>
<td>Claude Code's own helper for setting up a status line</td>
</tr>
</tbody></table>
<p>Known limitations:</p>
<ul>
<li><p>The <code>5h</code> and <code>wk</code> bars need a Pro or Max plan; the spend-limit data a Claude apps gateway can send isn't shown.</p>
</li>
<li><p>Tested only on macOS with Apple Terminal. Linux should behave the same; Windows is untested.</p>
</li>
<li><p>How <code>■</code> looks depends on your font.</p>
</li>
</ul>
<h2>Uninstall and recovery</h2>
<p>There's no uninstall script, so here are the manual steps.</p>
<p><strong>Easiest:</strong> run <code>/statusline remove it</code> inside Claude Code, which <a href="https://code.claude.com/docs/en/statusline">removes the <code>statusLine</code> setting</a> for you. Then delete the script with <code>rm ~/.claude/statusline-command.sh</code>.</p>
<p><strong>Or remove it by hand</strong>, keeping every other setting:</p>
<pre><code class="language-bash">jq 'del(.statusLine)' ~/.claude/settings.json &gt; /tmp/claude-settings.json \
  &amp;&amp; mv /tmp/claude-settings.json ~/.claude/settings.json
rm ~/.claude/statusline-command.sh
</code></pre>
<p><strong>Or restore your settings from the installer's backup:</strong></p>
<pre><code class="language-bash">ls ~/.claude/settings.json.bak-*            # pick the one from before the install
cp ~/.claude/settings.json.bak-&lt;date-time&gt; ~/.claude/settings.json
</code></pre>
<p>Restoring a backup also undoes any other settings changes you've made since, so the first option is usually better. If you had a status line script before, the installer kept it as <code>~/.claude/statusline-command.sh.bak-&lt;date-time&gt;</code>; rename it back to bring it back. Once you're happy, you can delete the <code>.bak-</code> files.</p>
<h2>FAQ</h2>
<p><strong>Does it make extra API requests or use tokens?</strong> No. The script only reads the data Claude Code passes to it and runs <code>git</code> locally. Claude Code <a href="https://code.claude.com/docs/en/statusline">states</a> that the status line doesn't consume API tokens.</p>
<p><strong>Does it work with an API key, Bedrock or Vertex?</strong> Mostly. The branch, session, model, context bar and estimated cost work. The <code>5h</code> and <code>wk</code> bars don't appear, because Claude Code only sends plan-limit data to Pro and Max subscribers.</p>
<p><strong>How often does it refresh?</strong> The script runs every second (<code>refreshInterval: 1</code>), and Claude Code also re-runs it after events such as each reply or a usage window resetting. The countdown and animation change every second; the usage and cost numbers change only when Claude Code receives new data.</p>
<p><strong>What happens when data is unavailable?</strong> That segment is left out. The script never fills in or estimates a value.</p>
<p><strong>How do I change the colours or bars?</strong> Edit <code>~/.claude/statusline-command.sh</code>: <code>BAR_WIDTH</code>, the <code>level()</code> thresholds and colours, and the <code>■</code> character in <code>bar()</code>. See <a href="#heading-configure-and-customize">Configure and customize</a>.</p>
<p><strong>How do I turn it off?</strong> Run <code>/statusline remove it</code>, or delete the <code>statusLine</code> entry from <code>~/.claude/settings.json</code> (see <a href="#heading-uninstall-and-recovery">Uninstall and recovery</a>). Claude Code goes back to its default bottom bar.</p>
<h2>Wrap-up</h2>
<ul>
<li><p>Claude Code sends the data; a short bash script decides how it looks.</p>
</li>
<li><p><code>refreshInterval: 1</code> keeps the countdown and animation live, while usage numbers update with each response.</p>
</li>
<li><p>Pick characters that line up with your text, keep numbers fixed-width, and colour by meaning.</p>
</li>
</ul>
<p>The full script is inside the <a href="https://siddarammetri.in/blog/claude-code-status-line/install.sh">installer</a>, so you can read it before running anything. The official <a href="https://code.claude.com/docs/en/statusline">status line documentation</a> lists every field Claude Code sends if you want to add your own segments. More posts are on <a href="https://siddarammetri.in/blogs">my blog</a>.</p>
<hr />
<p><em>Originally published on</em> <a href="https://siddarammetri.in/blogs/claude-code-custom-status-line"><em>siddarammetri.in</em></a><em>.</em></p>
]]></content:encoded></item><item><title><![CDATA[Stop Downloading Big Google Drive Folders in Your Browser]]></title><description><![CDATA[Download a 20 GB+ Google Drive folder with rclone and your own OAuth client: resumable, verified file by file, unzipped without overwriting.]]></description><link>https://siddarammetri.hashnode.dev/download-large-google-drive-folder-with-rclone</link><guid isPermaLink="true">https://siddarammetri.hashnode.dev/download-large-google-drive-folder-with-rclone</guid><category><![CDATA[rclone]]></category><category><![CDATA[Google Drive]]></category><category><![CDATA[google cloud]]></category><category><![CDATA[macOS]]></category><category><![CDATA[cli]]></category><dc:creator><![CDATA[Siddaram Metri]]></dc:creator><pubDate>Mon, 19 Oct 2026 10:03:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/678212dc41a3b15ad5b27b3b/8aee4c77-1d1e-41e5-8507-765afe003524.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I needed to move a Google Drive backup onto a new laptop: <strong>147 zip files, about 21 GB</strong>. Clicking "Download" in the browser is the obvious move, and for a folder this size it is the wrong one. This post shows the approach that worked first time: <strong>rclone</strong>, a free command-line tool, connected to Drive through <strong>your own Google OAuth client</strong>. The whole folder came down in about 12 minutes, was checked file by file against Drive, and was unzipped without overwriting anything already on the laptop.</p>
<p>Everything here is copy-and-paste. If you'd rather hand the whole job to an AI agent, there's a Claude Code shortcut just below.</p>
<h2>Shortcut: let Claude Code do it for you</h2>
<blockquote>
<p>Use <a href="https://www.anthropic.com/claude-code">Claude Code</a>? Open the <a href="https://siddarammetri.in/blogs/download-large-google-drive-folder-with-rclone">original post</a> and click <strong>Copy Claude Code prompt</strong>. Paste it into Claude Code with your folder link, and it runs every step below for you: it installs rclone, checks the folder, downloads it, verifies every file and unzips it, with a progress update every minute.</p>
<p>Not using Claude Code? Follow the steps below by hand.</p>
</blockquote>
<h2>Why not just click Download?</h2>
<p>When you download a folder from the Drive website, Google first <strong>re-packs it into zip files of about 2 GB each</strong>. With a folder of zips you end up with zips inside zips. Worse, if your connection drops at 87%, that part starts again from zero, and there's no easy way to check that everything arrived intact.</p>
<img src="https://siddarammetri.in/blog/rclone-google-drive/browser-vs-rclone.svg" alt="The browser re-zips and fails at 87%; rclone downloads file by file and skips finished files on a re-run" style="display:block;margin:0 auto" />

<p>rclone talks to the Drive API directly:</p>
<ul>
<li><p><strong>File by file.</strong> You get the same files that are on Drive, with no re-zipping.</p>
</li>
<li><p><strong>Resumable.</strong> Run the same command again and it skips everything already downloaded.</p>
</li>
<li><p><strong>Verifiable.</strong> <code>rclone check</code> compares every file with Drive after the download.</p>
</li>
<li><p><strong>Parallel.</strong> It downloads several files at once. I averaged about 33 MB/s.</p>
</li>
</ul>
<h2>What you need</h2>
<ul>
<li><p>A Mac with <a href="https://brew.sh">Homebrew</a>. Linux and Windows work too; only the install command differs.</p>
</li>
<li><p>Access to the Drive folder in your browser.</p>
</li>
<li><p>Enough free disk space, roughly <strong>2–3× the folder size</strong> if you'll unzip it. Check with:</p>
</li>
</ul>
<pre><code class="language-bash">df -h ~
</code></pre>
<h2>Step 1: Install rclone</h2>
<pre><code class="language-bash">brew install rclone
rclone version
</code></pre>
<p>It's an 86 MB install, and that's all you need locally.</p>
<h2>Step 2: Create your own Google OAuth client (recommended)</h2>
<p>rclone has a built-in Google login that works out of the box. Everyone uses it, though, so Google often rate-limits it, which shows up as <code>403 rateLimitExceeded</code> errors and slow speeds. Your own client avoids that. It's free, takes about 10 minutes, and you only do it once.</p>
<img src="https://siddarammetri.in/blog/rclone-google-drive/oauth-steps.svg" alt="Five steps: create a project, enable the Drive API, set up the consent screen, create a Desktop client, paste it into rclone" style="display:block;margin:0 auto" />

<p>Sign in with the Google account that can see the folder, then follow these links in order:</p>
<ol>
<li><p><strong>Create a project:</strong> <a href="https://console.cloud.google.com/projectcreate">console.cloud.google.com/projectcreate</a>. Name it something like <code>rclone-download</code>, click <strong>Create</strong>, and make sure it's selected in the top bar.</p>
</li>
<li><p><strong>Enable the Google Drive API:</strong> <a href="https://console.cloud.google.com/apis/library/drive.googleapis.com">console.cloud.google.com/apis/library/drive.googleapis.com</a>, then click <strong>Enable</strong>.</p>
</li>
<li><p><strong>Set up the consent screen:</strong> <a href="https://console.cloud.google.com/auth/overview">console.cloud.google.com/auth/overview</a>, then <strong>Get started</strong>. Enter an app name (<code>rclone</code>) and your email.</p>
<ul>
<li><p>On a company Google Workspace account, choose <strong>Internal</strong>. That's the simplest option.</p>
</li>
<li><p>On a personal account, choose <strong>External</strong>, then add your own email as a test user under <a href="https://console.cloud.google.com/auth/audience">Audience</a>.</p>
</li>
</ul>
</li>
<li><p><strong>Create the client:</strong> <a href="https://console.cloud.google.com/auth/clients/create">console.cloud.google.com/auth/clients/create</a>. Set the type to <strong>Desktop app</strong> and the name to <code>rclone</code>, then click <strong>Create</strong>. Copy the <strong>Client ID</strong> and <strong>Client secret</strong>.</p>
</li>
</ol>
<blockquote>
<p>If your company blocks creating Google Cloud projects, skip this step and leave the client ID and secret blank in step 3. rclone's built-in login still works, just more slowly.</p>
</blockquote>
<h2>Step 3: Connect rclone to Google Drive</h2>
<p>A single command creates the connection (rclone calls it a <em>remote</em>) and opens your browser to sign in:</p>
<pre><code class="language-bash">rclone config create gdrive drive \
  client_id=YOUR_CLIENT_ID.apps.googleusercontent.com \
  client_secret=YOUR_CLIENT_SECRET \
  scope=drive.readonly
</code></pre>
<p>Sign in, then click <strong>Allow</strong>. If Google warns that it "hasn't verified this app", click <strong>Continue</strong>. It's your own app.</p>
<p>Use <code>scope=drive.readonly</code> if you only need to download. rclone then can't change or delete anything in your Drive, even by accident. Use <code>scope=drive</code> only if you also plan to upload.</p>
<blockquote>
<p>Prefer menus? Run <code>rclone config</code>, choose <code>n</code> for a new remote, name it <code>gdrive</code>, pick <code>drive</code>, paste the ID and secret, and accept the defaults.</p>
</blockquote>
<h2>Step 4: Look before you download</h2>
<p>Take the <strong>folder ID</strong> from the Drive link, which is the part after <code>/folders/</code>:</p>
<pre><code class="language-text">https://drive.google.com/drive/folders/1AbCdEfGhIjKlMnOpQrStUvWxYz012345
                                       └──────────── folder ID ────────────┘
</code></pre>
<p>Then check what's inside without downloading anything:</p>
<pre><code class="language-bash">FOLDER_ID=1AbCdEfGhIjKlMnOpQrStUvWxYz012345

rclone size gdrive: --drive-root-folder-id $FOLDER_ID   # file count + total size
rclone lsf  gdrive: --drive-root-folder-id $FOLDER_ID   # top-level contents
</code></pre>
<p>This step paid off. A second link I had been sent contained the first folder as a subfolder, with the same Drive ID. Spotting that here saved downloading the same 21 GB twice.</p>
<h2>Step 5: Download</h2>
<pre><code class="language-bash">mkdir -p ~/Downloads/drive-data

caffeinate -i rclone copy gdrive: ~/Downloads/drive-data \
  --drive-root-folder-id $FOLDER_ID \
  --transfers 4 -P
</code></pre>
<img src="https://siddarammetri.in/blog/rclone-google-drive/rclone-progress.svg" alt="rclone copy progress: 12%, 38%, 65%, 92%, 100% at about 33 MiB/s, then 147 files with 0 errors" style="display:block;margin:0 auto" />

<table>
<thead>
<tr>
<th>Part</th>
<th>What it does</th>
</tr>
</thead>
<tbody><tr>
<td><code>caffeinate -i</code></td>
<td>Keeps your Mac awake until the command finishes (macOS only)</td>
</tr>
<tr>
<td><code>rclone copy</code></td>
<td>Copies new and changed files and never deletes anything locally</td>
</tr>
<tr>
<td><code>gdrive:</code></td>
<td>The remote you created in step 3</td>
</tr>
<tr>
<td><code>--drive-root-folder-id</code></td>
<td>Starts from your folder instead of the top of "My Drive"</td>
</tr>
<tr>
<td><code>--transfers 4</code></td>
<td>Downloads 4 files at the same time</td>
</tr>
<tr>
<td><code>-P</code></td>
<td>Shows live progress</td>
</tr>
</tbody></table>
<p><strong>If it stops partway</strong> because of Wi-Fi, sleep or a closed lid, run exactly the same command again. Finished files are skipped.</p>
<p>To keep a log file and run it in the background instead, swap <code>-P</code> for:</p>
<pre><code class="language-bash">--stats 30s --stats-one-line --log-file ~/Downloads/drive-data.log --log-level INFO
</code></pre>
<h2>Step 6: Verify every file</h2>
<pre><code class="language-bash">rclone check gdrive: ~/Downloads/drive-data \
  --drive-root-folder-id $FOLDER_ID --one-way
</code></pre>
<p>You want to see <code>0 differences found</code>. This compares sizes and checksums with Drive. It doesn't download anything again.</p>
<h2>Step 7: Unzip without overwriting anything</h2>
<p>If the folder is full of zips and some of those projects already exist on your laptop, don't unzip straight over them. My rule was simple: <strong>never overwrite</strong>. If a folder already exists, the copy from Drive gets a <code>-backup</code> suffix instead.</p>
<img src="https://siddarammetri.in/blog/rclone-google-drive/restore-flow.svg" alt="Download, verify, unzip, then if the folder exists save it as project-backup instead of overwriting" style="display:block;margin:0 auto" />

<pre><code class="language-bash">#!/bin/bash
# Unzip every zip in SRC into DEST. Never overwrites: clashes become name-backup, name-backup-2, ...
SRC=~/Downloads/drive-data
DEST=~/Documents/Restored
mkdir -p "$DEST"

free_path() {           # first unused path: name, name-backup, name-backup-2, ...
  local p="$1/$2" i=2
  [ -e "$p" ] || { echo "$p"; return; }
  p="$1/$2-backup"
  while [ -e "$p" ]; do p="$1/$2-backup-$i"; i=$((i+1)); done
  echo "$p"
}

for zip in "$SRC"/*.zip; do
  tmp=$(mktemp -d)
  unzip -q "$zip" -x '__MACOSX/*' -d "$tmp" || ditto -x -k "$zip" "$tmp"
  for item in "$tmp"/*; do
    target=$(free_path "$DEST" "$(basename "$item")")
    mv "$item" "$target"
    echo "$(basename "$zip") -&gt; $target" &gt;&gt; "$DEST/_restore-log.txt"
  done
  rmdir "$tmp"
done
</code></pre>
<p>Save it as <code>restore.sh</code>, set <code>SRC</code> and <code>DEST</code>, and run <code>bash restore.sh</code>. Keep the zips until you've opened a few projects and confirmed they look right. Then delete them to get the space back.</p>
<p><strong>Restored a JavaScript project?</strong> Backups almost never include <code>node_modules</code>, so the first <code>npm run dev</code> fails with <code>next: command not found</code>. Run <code>npm install</code> (or <code>bun install</code>) once in each project.</p>
<h2>Troubleshooting</h2>
<table>
<thead>
<tr>
<th>You see</th>
<th>Fix</th>
</tr>
</thead>
<tbody><tr>
<td><code>403 rateLimitExceeded</code>, slow speed</td>
<td>Use your own client ID (step 2). rclone retries on its own.</td>
</tr>
<tr>
<td><code>token expired</code> / <code>invalid_grant</code></td>
<td><code>rclone config reconnect gdrive:</code> and sign in again</td>
</tr>
<tr>
<td>"Google hasn't verified this app"</td>
<td>Click <strong>Continue</strong>. It's your own client.</td>
</tr>
<tr>
<td><code>rclone lsf</code> shows nothing</td>
<td>The ID is wrong, or the folder is shared with another account than the one you signed in with</td>
</tr>
<tr>
<td>You can't create a Cloud project</td>
<td>Your company blocks it. Leave the client ID and secret blank.</td>
</tr>
<tr>
<td>The download keeps stopping</td>
<td>Your Mac is sleeping. Keep <code>caffeinate -i</code> at the front of the command.</td>
</tr>
</tbody></table>
<h2>Clean up when you're done</h2>
<p>Once the download is finished, remove access you no longer need:</p>
<ol>
<li><p><strong>Delete the Cloud project:</strong> <a href="https://console.cloud.google.com/iam-admin/settings">console.cloud.google.com/iam-admin/settings</a>, select the project, then <strong>Shut down</strong>. This removes the client ID and secret too. Google keeps the project for 30 days, then deletes it.</p>
</li>
<li><p><strong>Revoke rclone's access:</strong> <a href="https://myaccount.google.com/connections">myaccount.google.com/connections</a>, then your app, then <strong>Delete all connections</strong>.</p>
</li>
<li><p><strong>Delete the saved login on your Mac:</strong></p>
</li>
</ol>
<pre><code class="language-bash">rclone config delete gdrive
</code></pre>
<p>rclone itself can stay installed. It's small, and next time you only need step 3 again.</p>
<h2>A note for work accounts</h2>
<p>On a company Google Workspace account, all of this is visible to your admins if they look. The Cloud project, the app you authorised and <strong>each file download</strong> are logged against your account, with the time and your IP address. Your Mac's name and the folder you saved to are not recorded. Downloading your own work files to your work laptop is ordinary activity, but if your company has rules about third-party apps, check them first.</p>
<h2>Wrap-up</h2>
<ul>
<li><p><strong>rclone + your own OAuth client</strong> turns a fragile 21 GB browser download into one reliable command.</p>
</li>
<li><p><strong>Look first</strong> (<code>rclone size</code>, <code>rclone lsf</code>), <strong>verify after</strong> (<code>rclone check</code>), and <strong>never overwrite</strong> when you unzip.</p>
</li>
<li><p><strong>Clean up the access</strong> when you're done.</p>
</li>
</ul>
<p>Or skip the typing: click <strong>Copy Claude Code prompt</strong> on the <a href="https://siddarammetri.in/blogs/download-large-google-drive-folder-with-rclone">original post</a>, paste it into Claude Code with your link, and watch it work through each step.</p>
<hr />
<p><em>Originally published on</em> <a href="https://siddarammetri.in/blogs/download-large-google-drive-folder-with-rclone"><em>siddarammetri.in</em></a><em>.</em></p>
]]></content:encoded></item></channel></rss>