statusLine updates are capped at 1 Hz — smooth animation is impossible

Status Open
Reported on v2.1.217
Maintainer reply None cached
Activity 0 comments · opened Jul 22, 2026

What I want

A status line that can update at a high frame rate — smooth motion, not discrete
state flips. Ideally something in the neighbourhood of the display's refresh
rate, opt-in, for authors who want to pay for it.

Right now it's capped at 1 Hz, and the cap is hard-coded in two places.

The cap

Checked against the 2.1.217 binary. The setting is validated with a min(1):

refreshInterval: v.number().min(1).optional().catch(void 0)
  .describe("Re-run the status line command every N seconds in addition to event-driven updates")

and clamped again where the interval hook is installed:

let G = u?.refreshInterval;
gc(U, G !== void 0 ? Math.max(1, G) * 1000 : null);

Measured

I set refreshInterval: 0.2, wrapped my status line in a script that logged a
millisecond timestamp on every invocation, and collected 157 ticks across idle
and active periods:

min 0.300s   median 1.000s   max 1.281s

0.2–0.3s :  1
0.3–0.4s :  4
0.4–0.5s :  3
0.5–0.6s :  3
0.6–0.7s :  6
0.7–0.8s :  3
0.9–1.0s : 47     ← the timer
1.0–1.1s : 86     ← the timer
1.2–1.3s :  3

The 133 ticks in the 0.9–1.1 s buckets are the timer, pinned at 1.000 s no matter
what I asked for.

The 20 ticks under 0.8 s are event-driven re-renders, which fire whenever
session state changes and are debounced at 300 ms — the fastest observed gap is
exactly 0.300 s, the debounce floor. Those only happen while the model is
actively working.

So the harness is already comfortable re-running the command 2–3×/second during
activity. The 1 Hz cap only binds when the session is idle, which is exactly when
a status line has the most room to show something interesting.

Why 1 Hz specifically doesn't work

At one frame per second, any continuous motion — a sweep, a pulse, a progress
shimmer — reads as broken rendering rather than as animation. The frames are too
far apart for the eye to connect them. In practice status-line authors are
limited to discrete on/off state flips.

I worked around it by animating only while the model is working (detected by
watching total_api_duration_ms advance), since that's when the event-driven
path lifts the effective rate to 2–3 fps, and rendering byte-identical output
when idle. It works, but it means the one thing that determines whether animation
is viable is a side effect I have to infer.

Two smaller notes

  • refreshInterval: 0.2 is silently rounded up, not rejected. Math.max(1, 0.2)

wins, so the setting looks accepted and does nothing. A warning or a doc note
would save people the experiment.

  • Output is only taken on process exit (let l = await r(); if (t.aborted) return; if (i(l), l) ...),

and an in-flight run is discarded when a new update supersedes it. That's a
reasonable design, but it isn't stated in the status-line docs, and it rules
out the workaround people will reach for first.

For reference

Per-frame cost matters here — measured on an M-series Mac, 500 iterations each:

| what it runs | cost per invocation |
| --- | --- |
| /bin/sh + echo hi | 3.29 ms |
| #!/usr/bin/env bash + echo hi | 6.43 ms |
| my real status line (unchanged tick, no jq/git) | 20.90 ms |

I'm not going to tell you how to build it — you'll know the constraints better
than I do. But I'd love to be able to opt into a genuinely high refresh rate.

Environment

  • Claude Code 2.1.217, macOS (darwin arm64), Apple Silicon
  • Reproduced with a plain bash status-line script

View original on GitHub ↗