Lining up the Claude Code status line with my powerlevel10k prompt: padding -2 and a 5-second refresh

I keep Claude Code in a tmux pane above a zsh pane, so its status line sits just above my zsh prompt, and the two almost matched. Both used the same powerlevel10k colours and separators, but Claude’s line started 2 columns further in and stopped wherever its text ran out. The zsh line ran the full width of the terminal, so the pair looked misaligned.

The fix was a dotted gap computed from $COLUMNS and "padding": -2, a value the docs don’t mention. Now both prompts start at column 0 and close on the same column, 134 of 135.

I asked Claude Code to do this. It made the measurements below in that session, and it drafted this post from them with the blog skill. I’m using “I” for the session as a whole, the same way as in my other Claude Code write-ups.

The setup

  • Claude Code 2.1.316, Linux, running inside tmux
  • zsh with powerlevel10k, POWERLEVEL9K_MODE=nerdfont-v3
  • A two-line p10k prompt with · as the gap character
  • An existing ~/.claude/statusline-p10k.sh that already drew p10k-style segments

The two lines looked like this. Pasting from the terminal lost the Nerd Font glyphs, so the separators are missing, and I shortened the dots and rule. The third line is the tmux pane border between the two panes:

1
2
3
4
  ╭─  .claude   Opus 5.5 · high
  ⏵⏵ auto mode on (shift+tab to cycle) · ← for agents
──2 ✳ Claude Code claude >─────────────────────────────────────────────────────
╭─  .claude ······································ ✔  Nov05 18:30:42  ─╮

So there were two differences: the 2-column indent, and no gap filling out to ─╮.

Step 1: what the docs say

The status line docs answered half of it. The script runs with its output captured, so tput cols can’t see the terminal, but Claude Code sets COLUMNS and LINES before running it. That is enough to compute a gap.

The indent looked worse:

This padding is in addition to the interface’s built-in spacing, so it controls relative indentation rather than absolute distance from the terminal edge.

Going by that, "padding": 0 would not reach the edge. I also didn’t know how many of the COLUMNS characters the status line would really display.

Step 2: measure with a ruler in a real Claude Code

Instead of guessing, I started real claude in a throwaway tmux session and used a ruler as its status line. --settings makes this a per-run override, so it leaves settings.json alone.

probe2.sh:

1
2
3
4
5
#!/usr/bin/env bash
cat >/dev/null
n=$((COLUMNS - ${OFF:-0}))
s=""; for ((i=1;i<=n;i++)); do s+=$((i%10)); done
printf '%s\n' "$s"

Running it and reading the screen back:

1
2
3
4
tmux new-session -d -s sltest -x 120 -y 30 -c ~/.claude
tmux send-keys -t sltest "claude --settings '{\"statusLine\":{\"type\":\"command\",\"command\":\"OFF=0 bash $PWD/probe2.sh\",\"padding\":0}}'" Enter
# wait for the first render, then look at what actually reached the screen
tmux capture-pane -p -t sltest | grep 123456789

The first run confirmed both worries:

1
  1234567890123456789012345678901234567890 ... 1234567890…

The pane came up 135 columns wide rather than the 120 I asked for. I never found out why, but COLUMNS reported 135, so all the numbers below use that. Varying the offset and the padding gave:

padding Characters printed Result
0 or unset 135 indented 2, truncated with …
0 or unset 133, 132 indented 2, truncated with …
0 or unset 131 indented 2, fits
-2 135 starts at column 0, fits
-3 135 the first character is cut off

The default padding really does leave only COLUMNS - 4 usable characters: 2 reserved on each side. A negative value is accepted, and -2 cancels the reservation exactly. -3 goes too far and clips the line.

I also tried a leading \r and a cursor-left escape to get out of the indent. Both runs produced no ruler at all. That was probably my shell quoting rather than Claude Code, but I didn’t look into it, because -2 already worked.

Step 3: copy the gap from the real prompt

Before writing any drawing code, I captured the actual p10k line with its escape codes. Trimmed:

1
tmux capture-pane -p -e -t sltest | grep -m1 '╭─' | sed 's/\x1b/\\e/g'
1
\e[38;5;240m╭─\e[38;5;254m\e[44m  .claude \e[34m\e[49m\e[38;5;240m·········\e[30m\e[32m\e[40m ✔ \e[37m\e[30m\e[47m Nov05 18:33:44  \e[38;5;240m\e[49m─╮\e[39m

This gave me three details to copy:

  • The dots are colour 240 and touch the separators, with no spaces around them.
  • ─╮ is also colour 240, and sits directly after the last segment.
  • The directory is plain colour 254. My script drew it bold in colour 255, which was simply wrong. It’s 254 at ~, at ~/.claude and one level deeper.

The fix

~/.claude/settings.json:

1
2
3
4
5
6
"statusLine": {
  "type": "command",
  "command": "bash ~/.claude/statusline-p10k.sh",
  "padding": -2,
  "refreshInterval": 5
}

The end of ~/.claude/statusline-p10k.sh, after the left segments are in $out and draw_right can render the right array (the full script is here):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
width() {  # visible columns: drop colour codes, count characters
  local plain
  plain=$(sed "s/$ESC\[[0-9;]*m//g" <<<"$1")
  echo "${#plain}"
}

# Fill the line like p10k's gap: ╭─ left ···· right ─╮, ending where the zsh prompt ends.
# Rightmost segments go first when the line does not fit.
suffix="$(fg 240)─╮$RESET"
cols=$(( ${COLUMNS:-80} - 1 ))   # zsh leaves the last column free
left_width=$(width "$out")
while :; do
  r=$(draw_right)
  gap=$(( cols - left_width - $(width "$r") - 2 ))
  (( gap >= 0 || ${#right[@]} == 0 )) && break
  unset 'right[-1]'
done

dots=""
if (( gap > 0 )); then
  printf -v dots '%*s' "$gap" ''
  dots=${dots// /·}
fi

printf '%s\n' "$out$(fg 240)$dots$RESET$r$suffix"

A few details that matter:

  • ${#plain} counts characters only under a UTF-8 locale. Mine is, the probe logged LC_ALL=en_US.UTF-8, but in a C locale it would count bytes and the gap would come out wrong.
  • The Nerd Font separators count as one column each. The final widths match, so that holds here.
  • The right segments are ordered by importance, so dropping from the end loses the 7-day usage first, then the 5-hour usage, then the context bar.

Two mistakes the real run caught

The line was one column too wide. With the gap in place, I compared the two prompts in real terminals (dots shortened here, widths as measured):

1
2
ZSH    (134): ╭─  .claude ···························· ✔  Nov05 18:34:44  ─╮
CLAUDE (135): ╭─  .claude ································· Opus 5.5 · high ─╮

I had read the zsh line as 135 wide, because my first measurement piped grep into wc -m, which counted the newline. It’s really 134: zsh leaves the last column free. Hence the - 1 in cols.

Resizing left the line stale. Claude Code doesn’t rerun the script when the terminal resizes. Shrinking to 100 columns gave a truncated line:

1
╭─  .claude ·····················································································…

Growing to 150 left it 135 wide, well short of the right edge. refreshInterval reruns the script on a timer as well as on events, and after setting it to 5 both cases came right within my 7-second wait.

Results

Claude Code&rsquo;s status line above the zsh prompt, first and last columns boxed

A terminal illustration rendered from real tmux capture-pane -e output of each prompt at 100 columns, not a screenshot. The two captures were taken separately and stacked in the order they appear on my screen. The boxes mark the shared first and last columns.

Check at 135 columns Before After
Start column 2 0, same as zsh
Line width ends with its text 134, same as zsh
After resizing to 100 truncated with … 99, no truncation
After resizing to 150 stays 135 wide 149, no truncation

The “before” resize rows were measured with padding: -2 and the gap already in place, just without refreshInterval.

The script on its own, with a sample payload that includes context and usage data:

1
2
3
4
5
for c in 135 100 60 30; do
  l=$(COLUMNS=$c bash ~/.claude/statusline-p10k.sh <<<"$SAMPLE")
  p=$(sed 's/\x1b\[[0-9;]*m//g' <<<"$l")   # strip colours, then measure
  echo "cols=$c width=${#p}"; printf '%s\n' "$p"
done
1
2
3
4
5
6
7
8
cols=135 width=134
╭─  .claude ··········································· Opus 5.5 · high  ctx ▓▓▓▓░░░░░░ 42%  5h 12% ↻05:20  7d 83% ↻Wed 20:26 ─╮
cols=100 width=99
╭─  .claude ········ Opus 5.5 · high  ctx ▓▓▓▓░░░░░░ 42%  5h 12% ↻05:20  7d 83% ↻Wed 20:26 ─╮
cols=60 width=59
╭─  .claude ···· Opus 5.5 · high  ctx ▓▓▓▓░░░░░░ 42% ─╮
cols=30 width=29
╭─  .claude ·············─╮

What this costs

  • padding: -2 is undocumented. It works on 2.1.316, but the docs only describe padding as extra space. If a later version rejects it, the indent comes back and the line ends in …. The repair is to delete the setting and change - 1 to - 4 in cols, which matches the 131 characters measured above.
  • refreshInterval runs the script every 5 seconds, including git status. It’s cheap in most repos, but a very large one might want a longer interval.

I kept Claude’s model, context and usage segments on the right instead of copying zsh’s ✔ and clock. A clock would be easy to add now that the line refreshes.

Takeaways

  1. Measure a UI with a ruler. Printing 1234567890... through the real program settled in a few runs what the docs left open, and turned up a working value they don’t list.
  2. Test the real program, not just the script. Piping JSON into the script gave perfect widths. The stale resize only appeared inside a running Claude Code.
  3. Copy from capture-pane -e. The real escape codes showed the colours and spacing directly, including a bold directory I’d been getting wrong.
  4. wc -m counts the newline. Use printf '%s' when a width has to be exact. A similar width-sensitive status bar problem came up with tmux on my phone.

References

相关内容

william 支付宝支付宝
william 微信微信
0%