TutorialIA

La barra de estado de Claude Code es un script. No la congeles.

Cómo configurar la barra de estado de Claude Code: JSON por stdin, settings.json y los campos que importan. Un generador copia un prompt, no un script congelado.

Manuel Hedinger
8 min de lectura

Compacto demasiado tarde. El límite de cinco horas lo noto cuando la siguiente respuesta es un muro de pago. Qué modelo cambié hace tres mensajes, ya lo he olvidado. Todo eso está ya en la sesión. Claude Code simplemente no lo pone donde miro.

Para eso está la barra de estado.

La respuesta corta

La barra de estado es una fila encima de las insignias del pie. Claude Code ejecuta un comando, le pasa la sesión actual como JSON por stdin y pinta lo que el comando escribe en stdout. No gasta tokens de API. No sustituye las insignias. Sí oculta la mayoría de los atajos de teclado — esc to interrupt, ? for shortcuts, el aviso de mantener espacio para hablar — así que si vives de ellos, ese es el precio.

Hay tres caminos. Describe la línea con /statusline y deja que Claude escriba el script en ~/.claude/. Pon tú un objeto statusLine en ~/.claude/settings.json. O monta el diseño en el generador de barra de estado y pega el prompt. El generador no emite un script. Un script publicado en un post se queda viejo en cuanto Anthropic añade un campo. Un prompt se lee contra la documentación que Claude Code ya tiene.

La guía oficial es Customize your status line. Lo que sigue es la versión que me habría gustado tener: qué mostrar, qué dejar quieto y las partes que fallan en silencio.

Hay tres caminos para una barra de estado: describirla, escribir settings.json o componer un prompt en el generador.

Preguntar a /statusline

Algo así:

/statusline show model name and context percentage with a progress bar

Claude genera un script en ~/.claude/ y escribe los ajustes. Confirma los prompts de edición de archivo si te los pide. Es el primer paso correcto si nunca has tenido una barra de estado y todavía no te importa cómo funciona por dentro.

Para quitarla después, /statusline delete (o clear, o remove it) sirve. O borra tú el campo statusLine de settings.json.

Escribir los ajustes

Los ajustes de usuario viven en ~/.claude/settings.json. Los del proyecto también valen. La forma es pequeña:

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

type es "command". command es la ruta de un script o un fragmento de shell. Los ajustes se recargan al guardar; no reinicias Claude Code.

padding es espacio horizontal extra en caracteres, encima del margen incorporado. Por defecto 0.

refreshInterval vuelve a ejecutar el comando cada N segundos, además de las actualizaciones por eventos. Mínimo 1. Ponlo para un reloj, o cuando subagentes en segundo plano cambian el estado de git mientras la sesión principal está idle. Déjalo sin poner si solo te importan las respuestas.

hideVimModeIndicator oculta el -- INSERT -- de fábrica bajo el prompt. Actívalo cuando tu script ya imprime vim.mode, para no ver el modo dos veces.

El comando corre en una shell, así que para el primer check basta una línea:

{
  "statusLine": {
    "type": "command",
    "command": "jq -r '\"[\\(.model.display_name)] \\(.context_window.used_percentage // 0)% context\"'"
  }
}

Vas a necesitar jq. En Windows, escribe la ruta con barras inclinadas (C:/Users/you/.claude/statusline.sh). Git Bash trata las barras invertidas como escapes y el comando falla sin nada en pantalla.

Compónlo, y que Claude escriba el archivo

No guardo un gist de statusline.sh. El JSON de stdin crece. El script del año pasado no conoce rate_limits, ni pr.kind para GitLab, ni COLUMNS. El seguimiento que de verdad quiero es «now make the bar wider» o «add the open PR».

Por eso el generador de barra de estado copia un prompt, no un archivo. Arrastras los segmentos que quieres — directorio, rama, modelo, contexto, los límites de 5 horas y 7 días, coste, un reloj, líneas cambiadas, el PR, el worktree —, eliges una paleta y pegas el prompt en inglés en una sesión. Claude escribe el script, apunta settings.json hacia él y sigue ahí para el siguiente ajuste.

Corre en el navegador. Nada de lo que colocas se sube a ningún sitio.

Cómo se mueven los datos

Claude Code serializa la sesión en curso y la mete por un pipe. Tu comando imprime una línea. Esa línea es la fila.

Claude Code escribe la sesión como JSON, tu comando imprime una línea, la fila aparece encima del pie. Sin tokens.

El comando corre una vez al arrancar una sesión, también al reanudarla. Después vuelve a correr cuando:

  • llega un mensaje nuevo del asistente
  • termina /compact
  • cambia el modo de permisos
  • se conmuta el modo vim
  • cambias command en los ajustes
  • dispara un temporizador refreshInterval, si lo pusiste

Las actualizaciones se agrupan a 300ms. Un cambio en command se salta esa espera y corre al momento. Si llega una actualización nueva mientras el script sigue en marcha, se cancela la ejecución en vuelo. Editas el script en disco y el siguiente disparo lo recoge; no hay recarga extra.

Las actualizaciones por eventos se callan cuando la sesión principal está idle, por ejemplo mientras un coordinador espera a subagentes en segundo plano. Para eso está refreshInterval.

Puedes imprimir varias líneas. Puedes usar colores ANSI. Puedes envolver texto en secuencias OSC 8 para que sea clicable (Cmd-clic en macOS, Ctrl-clic en el resto) en iTerm2, Kitty o WezTerm. Terminal.app no hace enlaces clicables. Si las secuencias aparecen como \e]8;; literal, usa printf '%b', no echo -e.

No llames a tput cols dentro del script. Claude Code captura stdout en vez de engancharte al terminal, así que la detección de ancho desde dentro del proceso va a ciegas. Lee COLUMNS y LINES. Se asignan antes de correr el comando, a partir de la versión 2.1.153.

La fila se oculta durante el autocompletado, el menú de ayuda y los prompts de permiso. Fuera de pantalla completa, las notificaciones comparten la misma fila y te recortan en un terminal estrecho.

Qué merece la pena mostrar

El JSON es grande. En una fila de 80 columnas la mayor parte es ruido.

Contexto. context_window.used_percentage es el campo. Se calcula solo con tokens de entrada (input_tokens + cache_creation_input_tokens + cache_read_input_tokens). Los tokens de salida no entran. Si calculas el porcentaje tú a partir de current_usage, usa la misma fórmula o no coincidirás con /context. current_usage es null antes de la primera llamada a la API, y otra vez después de /compact hasta que la siguiente respuesta lo rellena. Lo mismo para los campos de porcentaje al principio de una sesión. Fallback en jq: // 0.

La ventana por defecto es 200k tokens, o 1M en modelos de contexto extendido. exceeds_200k_tokens es un umbral fijo, no «la ventana está llena».

Coste. cost.total_cost_usd es una estimación en el cliente. No es tu factura. Vuelve a 0 $ con /clear (antes de v2.1.211 se arrastraba, y confundía). total_duration_ms es reloj de pared desde que arrancó la sesión; total_api_duration_ms es el tiempo esperando a la API.

Límites de ritmo. rate_limits.five_hour y rate_limits.seven_day existen para Claude.ai Pro/Max después de la primera respuesta de la API. Cada ventana puede faltar por su cuenta. La ausencia es «no dibujes el segmento», no 0 %.

Git. En el JSON no hay git.branch. Ejecutas git tú. Eso es lento en un repo grande, y el script corre a menudo, así que cachea. Usa session_id en el nombre del archivo de caché, no $$ ni os.getpid(): cambian en cada invocación y la caché nunca acierta. Sesiones concurrentes en repos distintos no deben compartir archivo.

workspace.git_worktree está puesto cuando estás en un worktree enlazado. worktree.* es otro objeto, presente solo durante una sesión worktree de Claude Code.

Lo demás que uso de verdad. model.display_name más effort.level. workspace.current_dir (el mismo valor que cwd; prefiere el campo anidado). pr.number y pr.review_state cuando hay un PR o un merge request de GitLab abierto en la rama. session_name si nombraste la sesión; el nombre por defecto al estilo my-app-3f no lo rellena. vim.mode si usas el modo vim.

Un campo ausente no es lo mismo que un campo en null. Trata los dos.

Un primer script

Bash, en macOS y Linux. Hazlo ejecutable (chmod +x ~/.claude/statusline.sh) y apunta command hacia él.

#!/bin/bash
input=$(cat)
 
MODEL=$(echo "$input" | jq -r '.model.display_name')
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
 
echo "[$MODEL] ${DIR##*/} | ${PCT}% context"

Pruébalo sin abrir Claude Code:

echo '{"model":{"display_name":"Opus"},"workspace":{"current_dir":"/home/user/project"},"context_window":{"used_percentage":25},"session_id":"test"}' | ./statusline.sh

Mantén corta la línea impresa. La barra no es ancha, y en un terminal estrecho las notificaciones de la derecha se la comen.

Cuando se queda en blanco

La lista de siempre, en el orden en que yo miro:

  1. ¿El script es ejecutable y escribe en stdout, no en stderr?
  2. ¿Aceptaste el diálogo de confianza del workspace? Hasta entonces la fila se queda vacía y claude --debug registra que se saltó el comando.
  3. En Windows, ¿Git Bash se comió una barra invertida en la ruta?
  4. ¿Estás mirando nulos antes de la primera respuesta? Fallbacks, y espera un turno.
  5. ¿Alguien puso disableAllHooks? Fuera de ajustes gestionados eso desactiva una barra de usuario. allowManagedHooksOnly en ajustes de la organización ignora la tuya en silencio.

claude --debug registra el código de salida y el stderr de la primera invocación de una sesión. Pedirle a Claude que ejecute el comando statusLine contra tu archivo de ajustes también saca el error antes que quedarte mirando una fila vacía.

Si después de varios mensajes ves -- o valores vacíos, reinicia una vez. Si los enlaces OSC 8 se pintan como texto, mira el terminal y prueba FORCE_HYPERLINK=1 claude.

Lo que yo hago correr

Tres filas. Directorio y rama a la izquierda, un reloj a la derecha. Modelo a la izquierda de la fila dos, contexto a la derecha. El límite de 5 horas en la fila tres, el de 7 días enfrente. Es el preset «Mine» del generador. Me importa compactar antes de que la ventana se llene, y el tope semanal antes del viernes por la tarde.

Sigo sin meter el script en el repo. Cuando quiero otro segmento pego un prompt nuevo. En el generador decido el diseño. Claude Code escribe el archivo.

Si quieres que el pie crezca insignias clicables cuando aparece un ID en la conversación, eso es otro ajuste: footerLinksRegexes. Sin script.

Si una barra de estado te está dando guerra y no quieres pasar la noche con códigos ANSI, escríbeme. A veces la respuesta es una línea de jq de 40 caracteres.