Customizable status line
Feature Request
Problem
Claude Code has a built-in customizable status line feature that allows users to display useful real-time information at the bottom of the terminal UI — token usage, model name, rate limits, context window, git branch, etc.
This is configured via a simple shell script hook in settings:
{
"statusLine": {
"type": "command",
"command": "/bin/bash ~/.claude/statusline.sh",
"padding": 0
}
}
The script receives JSON context on stdin and returns formatted text. This is incredibly useful for power users who want to monitor their session at a glance.
Codex CLI currently has no equivalent mechanism. There's no way to display custom information in the TUI — no status line, no hooks, no plugin system for UI customization.
Proposed Solution
Add a statusLine (or similar) configuration option to ~/.codex/config.toml that allows running an external command and rendering its output in the Codex TUI (e.g., at the bottom of the screen).
Something like:
[status_line]
command = "/bin/bash ~/.codex/statusline.sh"
refresh_interval_secs = 10
The command would receive session context (model, token count, etc.) as JSON on stdin and return a single line of text (with ANSI colors) on stdout.
Use Cases
- Display current model and token/context usage
- Show API rate limit status and reset times
- Display git branch and repo info
- Show project-specific metadata
- Integration with external monitoring tools
Why This Matters
For users who switch between Claude Code and Codex, the lack of a status line in Codex is a noticeable gap. The status line is a low-cost, high-value feature that significantly improves the interactive experience.
41 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Have you tried the
/statuslinecommand? It's a little less flexible than an arbitrary script, but it's much easier to configure. Does this meet your needs?I'd like to be able to see my jj-vcs status (change id etc.) in the status line.
It's likely not a small task to invest in making jj a core part of the product, but allowing a hook like this seems like a reasonable compromise https://github.com/openai/codex/issues/16921 seems like they have a good solution to this with a proposed PR ready to review
The current exposed features have significantly enhanced the functionality, and they can meet the needs of most users in various scenarios. However, I still hope to be able to add an extension point similar to the Statusline of CC CLI. It can store some user-defined data. If it can be made available, I would be very grateful.
Found this while researching this exact idea.
/statusline is actually a decent compromise, though I would still like to have control over the wording and order for conciseness.
For example, I have my Claude one at
5H: 96% | 7D: 100% | context: 89% | Sonnet 4.6
Since I use a screen reader, being able to set the order is important to me.
The built-in
/statuslinecommand is useful, but it does not fully cover the use case of a dynamically refreshed status provider. The gap I am running into is less "show one more built-in field" and more "let the user define what the statusline and/or terminal title should say while a session is running."Useful shape:
This would make
/statuslinea good default for most users while still giving power users a narrow extension point for project-specific or workflow-specific context.Adding another +1 for command-driven customization specifically, not just selecting built-in
/statuslinefields.The current
/statuslineis useful, but the gap vs Claude Code is the extension point: let users run a command/script, pass Codex session context as JSON on stdin, and render stdout with safe timeout/failure behavior.Use cases:
A built-in
/statuslinedefault and an opt-in command provider could coexist: default remains simple, power users get a narrow, predictable extension hook.+1
Thanks, I noticed #17827 is very closely related.
I am fine with this issue being treated as a duplicate if that helps tracking. The main point I wanted to emphasize is the same as the later comments in #17827: the built-in
/statuslinepicker is useful, but the missing piece is a command-driven extension point.For my use case, the important part is not just adding more built-in fields. It is allowing a user-provided command to receive Codex session context as JSON on stdin and render stdout back into the TUI, preserving ANSI colors and custom formatting, with timeout/failure safeguards.
That would let the built-in
/statuslineremain the simple default while enabling advanced users and community tools to build richer status lines./statusline customize is ready. it's just i can't change the color of the statusline...
when will it get merged to main?
<img width="437" height="26" alt="Image" src="https://github.com/user-attachments/assets/70173c24-3343-40e7-9e75-d85a2295a6bc" />
I just finished customizing my statusline in another app and stumbled upon this request. This is reminiscent of the old days of internet quotas. I used to track with over/under indicators like I've done here.
Adding a similar scripting ability to Codex is a +1 from me.
Adding another concrete use case for command-backed status-line support:
I need to show local coordination/watch health in the Codex TUI footer, e.g.
watchers:livewatchers:stalewatchers:deadThe status comes from a local script that checks a daemon PID, output freshness, and pending inbox events. Today I can approximate this with
UserPromptSubmithooks that emitsystemMessage, but that is not equivalent: it only appears at turn boundaries and only as transient transcript output. The operator still has no persistent footer signal that the watcher is healthy while working.The current
[tui].status_line = [...]built-in picker is useful for Codex-owned state, but this is external workflow state. A command-backed status-line item or banner would solve it cleanly:Key requirements from this use case:
This would let Codex support project-local operational telemetry without expanding the built-in status enum for every workflow-specific field.
+1. I maintain token-tracker, which renders a rich quota / context / cost statusline on Claude Code via its official StatusLine API — but on Codex I'm stuck with the default style, since there's no custom hook. Same data locally, no way to render it.
!Claude Code statusline
Copying Claude Code's design would close the gap — a
commandthat gets session JSON on stdin and prints a styled string to stdout:Keep the enum items as default, treat
commandas opt-in. Happy to test a branch.+1 — this is the single biggest gap for me when switching between Claude Code and Codex.
A concrete reference that this is both feasible and high-value: I maintain
usage, a macOS menu-bar/TUI tool that pins Claude Code + Codex quota to the screen (all from local files — no API calls).On the Claude Code side it ships exactly the
statusLinecommand hook proposed here, and renders something like:…with unicode meters, per-threshold colors, and a derived "time-until-reset" — all from a single script that reads local files off stdin.
On the Codex side we're limited to the fixed
tui.status_lineenum, so the best we can offer users is plain text:Same underlying data, but we can't draw the meters / colors / countdowns because there's no command-backed hook to render arbitrary output.
The proposed
[status_line] command = "..."shape (JSON context on stdin → one ANSI line on stdout) is exactly right — it mirrors Claude Code 1:1, so existing scripts and tools port over with near-zero friction. If this lands, we'd ship Codex support for it the same week. 🙏I've prototyped a minimal version of this and would be glad to contribute it if the team is open to an invited PR (I see contributions are invitation-only — happy to follow whatever process you prefer).
Design — one self-contained
[tui]key, with no change to the existingstatus_lineitem list:The command is fed session context as JSON on stdin (
model,reasoning_effort,cwd,git_branch,session_id,approval_mode,sandbox_mode,context_used_pct/context_remaining_pct,context_window,codex_version) and its first stdout line is rendered as a status-line segment — mirroring Claude Code'sstatusLine.command. Execution is async / non-blocking: it reuses the existing hooks command-runner pattern (configurable timeout,kill_on_drop), re-runs on an interval, and the output is sanitized to a single escape-free line so it can't corrupt the TUI.It's implemented against
main, kept intentionally small (no/statuslinepicker changes), with config-deserialize + TUI render tests passing andcargo fmt/clippyclean.A couple of things I'd want the team's steer on before any PR:
Happy to share the branch or adjust the design to fit your roadmap. Thanks!
Command-backed status lines work if the contract is small and bounded. Send JSON with session id, model, cwd, git branch, approval/sandbox mode, context usage, quota snapshot, and version. Enforce timeout, refresh interval, max output width, and stale-output behavior. That lets local quota/watch tools render state without giving the command transcript access or blocking the TUI.
---
_Generated with ax._
For anyone who mainly wants local host telemetry in the meantime, I published a small tmux-based workaround: https://github.com/AlanineX/codex-host-status
It does not replace the native feature request here. It wraps interactive
codexsessions, leaves subcommands likecodex exec/codex doctoruntouched, and renders a compact status bar like:🐍 base ⚙ CPU 4% 41C 🎮 GPU 2% 43C 0.6G 💾 RAM 10.1/47G 🕒 14:29This covers my local CPU/GPU/RAM visibility use case while waiting for a real command-backed
tui.status_linehook in Codex.+1 on this. I shipped a workaround for the same use case as an open-source CLI (token-tracker) and want to share what users actually need vs. what the built-in
/statuslinecan express today.What we ended up doing: since Codex has no command-backed statusline hook, we inject a two-line "faux statusline" via the
Stophook, returningsystemMessageafter every turn. It renders with 24-bit truecolor, shows project + branch + diff + session tokens + cost + 5h/7d quota + context %, and follows the active theme. (Verified: does not enter the model context.)!Codex faux statusline screenshot
Why
/statuslinewasn't enough:What would unblock the whole class of tools: exactly what Claude Code does — accept a command/script-based statusline (stdin JSON in, formatted text out). Today we're stuck with a hidden Stop-hook workaround that renders in the response area (not the bottom bar), depends on Stop-hook + systemMessage being stable, and is hard for new users to discover.
Happy to share implementation details if useful. Thanks for considering.
+1. I run a workflow/governance tool that's meant to work across different agents, but it leans heavily on Claude Code's statusLine. We keep a little state file (active task, pipeline stage, drift, context left) that the hooks update, and the statusline just renders it. Codex already has the hooks now, there's just nowhere to show it natively. Would love to port it over: an opt-in { type = "command", command, timeout_ms } that gets the session JSON on stdin and renders stdout, with a timeout.
Thanks for the great work on this!
Related to this, here's a small tool. It is not a live status line, but it helps inspect local Codex/Claude session data so token usage and session details are less opaque, without API keys or sending logs anywhere.
Open source:
https://github.com/pzarzycki/token-companion
If you like it - leave a star; submit an issue or request, or even a PR!
Just want to add in that this would be extremely useful for improving the way the current statusline renders items when displaying multiple items. Codex cli is easiest to read when not set to the entire screen width, but then items overflow:
Being able to style these, and/or have some of them only appear at desired thresholds (like weekly/5h only appearing below a certain remaining level) enhances the statusline massively, it could instead render as
This is the same statusline information, but it now renders all the information (weekly is cut off originally).
Allowing the user to do this would be fantastic.
+1
We ship a tool that integrates with coding agents and the lack of a flexible status line is quite painful to our customers - it means they've got no way to display progress information from within our stack when the agent is driving our tool. It makes the workflow more opaque for them as they can't tell what the agent is doing or why.
Ideally we'd be able to supply a default script for status line behaviour that less advanced users can just configure, with more advanced users being able to script whatever they want from our feedback and other sources.
Edit to add: Alternatively, the ability to supply a JSON blob for formatting / filtering within the agent's status line config would also work well.
The current status line configuration is limited to built-in fields and does not support customer-specific workflow needs, such as command-backed content, semantic colors, or conditional indicators. Please consider supporting an extensible status line API.
I'd like to see this too.
Adding a concrete, shipping use case for a command-backed status line (distinct from picking more built-in fields).
We ship
$codex-usageas a Codex-native workflow. It reads rate-limit snapshots through the authenticatedcodex app-serverprotocol (it never opensauth.json) and renders a compact pace indicator against a 95%-utilization target. Its default output is already exactly one status line:i.e.
<project>(<branch>) <pace-glyph> <±pace-vs-target>% (<resets-in>), and it also emits a machine-readable payload a hook could consume directly:The one thing we can't do is surface this persistently in the TUI.
[tui].status_lineis a fixed enum of built-in item ids, so today the pace line only lives in an on-demand invocation or a transient session-start message — it can't stay pinned in the footer the way Claude Code'sstatusLine.commandkeeps a custom renderer live.The missing piece is exactly the extension point in this issue: Codex pipes session/rate-limit JSON to a user command on stdin and renders its stdout (first line, ANSI-aware) in the footer, with a timeout + fail-open so a slow or failing command never blocks the TUI. Additive to the existing field picker, roughly:
PR #10170 already demonstrated a working implementation; the status surface has since been refactored into
tui/src/bottom_pane/, so it would need a fresh pass on the current architecture rather than a straight rebase. Happy to help build and test it against a real renderer ($codex-usage) if there's maintainer appetite.I put together a working implementation on today's
bottom_panearchitecture: compare diff.It adds a
customstatus-line item backed by[tui].status_line_command(argv) — Codex runs it on a timer, pipes a small JSON session snapshot ({cwd, git_branch, model}) on stdin, and renders the first stdout line, with a 1.5s timeout + kill-on-drop and fail-open (keeps the last good value), mirroring the async workspace-headline item. A snapshot diff re-runs it immediately on a branch/dir switch so the footer isn't stuck on stale data. Rebases cleanly onmain;clippy/fmtclean; the command-runner unit tests plus the fullstatus_linetui suite pass.This repo limits opening PRs to collaborators, so I can't open one directly — happy to if a maintainer enables it, or the branch is here to cherry-pick:
shenxingy:feat/status-line-command.+1. Use case: side-by-side multi-terminal agent workflows (Claude Code + Codex CLI in split panes). We render cross-session mailbox/peer badges in Claude Code via its
statusLinecommand hook, but Codex's[tui].status_lineonly accepts built-in item identifiers — so the Codex pane can't show a symmetric indicator. A custom command item (or a single script-fed text item) would cover this.I hit this limitation on Codex CLI 0.145.0 while trying to reproduce a compact Claude Code-style footer.
The current implementation makes even small presentation changes impossible:
tui.status_lineaccepts only the built-inStatusLineItemIDs." | "is accepted by the TOML/schema layer but later ignored by the TUI as an invalid item." · "./usage.--strict-configdoes not catch invalid status-line IDs, so the configuration can validate successfully and still be partially ignored at runtime.Please consider supporting:
This is a small customization surface, but the current fixed renderer creates a surprisingly large usability gap for users moving between Claude Code and Codex.
I have a working implementation of this and would like to offer it — happy to adjust the design before opening a PR, and mindful that external PRs here are by invitation, so posting the approach first.
Shape: one new config table plus one new item id, everything else reuses existing machinery:
Key design points:
statusLinecontract (model.display_name,workspace.current_dir,context_window.*,rate_limits.*, snake_case, same omit/null rules,COLUMNS/LINESin env), so the existing ecosystem of third-party status-line scripts runs on both tools unmodified. Codex extensions:permission_mode,git_branch.notify— the key is stripped from project-local.codex/config layers, so a cloned repo can never register a command.Branch with the full implementation and test battery: feat/status-line-command. If maintainers are open to it I'd appreciate design feedback — especially on the config key name, the auto-append rule, and whether the Claude-Code-compatible stdin shape is acceptable — and can open the PR whenever invited.
Credit where it's due — @shenxingy proposed this same shape (
[tui.status_line_command]+ acustomitem, mirroring the workspace-headline async pattern) a week before I posted, and even landed on the same branch name. I found the convergence after posting; their comment deserves the priority.Where mine may still add something on top of theirs: the stdin payload is the full Claude Code
statusLineschema rather than a minimal snapshot (so existing third-party scripts run unmodified), plus a security posture that strips the key from project-local.codex/layers so a cloned repo can't register a command, output sanitization (ANSI/C0/C1/bidi + length caps), a circuit breaker after repeated failures, and the hooks command-runner extracted into a shared helper — which also fixes a real bug where the stdin write sat outside its own timeout.Happy for maintainers to take whichever branch is more useful, or for the two to be merged — @shenxingy, glad to combine if you're up for it.
Thanks for the thoughtful follow-up and for the credit — I’m happy to collaborate.
Your Claude Code-compatible payload and the additional security/sanitization work are valuable additions. My branch was intentionally kept minimal, so it may still be useful as a smaller reference if the maintainers prefer a narrower change.
Since external PRs are invitation-only and the maintainers haven’t selected a design yet, I suggest we wait for their guidance before merging the branches. If a PR is invited, I’d be glad to help review and test the combined implementation against Clade’s
codex-usagerenderer, and to contribute whichever parts of my branch are useful.This feature request is clearly in demand: the thread already contains many practical use cases, and the two recent implementations show that a command-backed status line can be added without changing the TUI core model.
After comparing
shenxingy:feat/status-line-commandandL-svg-png:feat/status-line-command, I thinkL-svg-pngis the stronger foundation. The nested configuration, preventing command execution from project-local config, output sanitization and caps, timeouts, and failure handling address important security and reliability concerns.That said, the current scope, around 45 files including a refactor of the shared command execution mechanism, may make review harder. For a first version, it may be worth keeping only the necessary functionality and splitting the shared refactor into a separate PR. From
shenxingy's implementation, I would keep argv-based command execution: it is more predictable and safer than implicitly running a shell string.It would also be useful to version the script input payload. Claude Code compatibility should be defined as a supported subset, rather than an open-ended commitment to fully mirror another project's contract. Could maintainers clarify the minimum set of requirements they would accept for a v1? That would help the authors converge on one implementation and get it merged sooner.
@etraut-openai, since you previously suggested
/statuslineas the supported path, could you clarify whether the team would consider a command-backed custom segment for a minimal v1, and which constraints or requirements would be mandatory?Thanks @unger1984 for the detailed comparison, and agreed with @shenxingy on waiting for maintainer guidance before converging branches.
All three suggestions sound right to me, and I've checked each against my branch so this isn't hand-waving:
notify's untagged bool-or-array config shape):commandcan accept a TOML array (exec'd directly, no shell) alongside the string form for compatibility with existing Claude Code scripts. The argv path inherits all the existing hardening unchanged (single timeout envelope, kill-on-drop, capped concurrent pipe reads) — roughly a 150–200 line delta.payload_version: 1integer field (the existingversionfield carries the CLI version, matching Claude Code's), bumped only on breaking changes — plus Claude Code compatibility defined as a documented, guaranteed subset of fields rather than an open-ended commitment to mirror another project's contract. Scripts must tolerate absent fields; Codex-specific additions stay documented extensions.Whatever constraints the maintainers set for a v1, between the two branches the pieces are ready.
+1. Another concrete use case the built-in item list can't cover: when was this conversation last active.
I keep several agent TUI sessions open across days (Codex CLI, Claude Code, others, in separate terminal tabs). Coming back the next day, I can't tell which tab holds which conversation or how stale each one is. In Claude Code the command-backed status line solves it in three lines of shell — the script stats the transcript file's mtime and renders the last-response time (e.g.
⏱ 31/07/2026 12:55) in the footer, formatted for my locale.In Codex this is currently impossible: neither the
status_linenor theterminal_titleitem sets include any clock/timestamp/last-activity item, unknown identifiers are warned about rather than rendered literally, and hook-based workarounds (writing the terminal title from a Stop hook) get repainted over by the TUI's own title management.A command-backed status line as proposed here would cover this and every similar niche without OpenAI having to anticipate them. Failing that, even a built-in
last-activityitem (timestamp of the last turn, locale-aware) would go a long way for multi-session workflows.My use case is related to this issue, but I need more than a user-customizable status line.
I would like the agent itself to update a persistent semantic task status in the TUI. For example:
Atlas (repository understanding) · Investigating · 2/5
Current: Comparing CLI parsing logic with the README
The status should not be emitted as normal assistant text, because it is quickly pushed out of view by tool output. Instead, the agent should call a structured tool such as
set_task_status, which emits a dedicated event consumed by the TUI.Suggested fields:
The TUI could also derive fallback status from plan updates, active commands, approval requests, and turn completion events.
Related: #21327, #22099.
Would you prefer this to be tracked as part of this status-line issue, or as a separate issue focused on agent-controlled task status?
Concrete production use case for this extension:
My session-start hook obtains the active Vornik RAG context:
Vornik: popron-devops:.../devops/slavaToday it can only print this as transcript text; it cannot add it to the persistent Codex footer. That makes it easy to lose the active AI-ops project/scope when several Codex sessions are open.
Suggested contract:
customstatus-line item placed alongside built-ins in[tui].status_line.This would support Vornik scope, CI/build state, ticket identifiers, and similar local workflow metadata without adding each integration as a built-in item.
I would also say it's not just about use cases. It's about preferences. In other words, I don't want the string "Context 42% used", I want "Ctx: 42%". I don't have infinite space in my status line. I need to squeeze more information in there. Same use case, different preference. Let people use this in a way that suits them, not just in the ways the top-down approach allows.
Two notes on the command-backed direction — one a defect worth fixing before it ships, one a triage observation.
The project-local protection is key-specific, and that's a trap
Every prototype here correctly refuses to let a project-local
.codex/layer register a command — otherwise cloning a repo would run its author's shell command on your machine. In the current shape that's enforced by stripping one named key,tui.status_line_command, from project layers.That works today and breaks quietly later. The protection isn't attached to the dangerous property, it's attached to a spelling. Any future key that spawns a process — a second provider, a segment-scoped command, a per-item override, an env-var hook — is unprotected until someone remembers to add it to the strip list. Nothing fails loudly when they don't. The config parses, the feature works, and a cloned repo gets code execution.
The fix is small if it happens now and awkward if it happens after the fact: make "spawns a process" a property the config schema carries, and drive the project-layer strip off that property rather than a list of names. Same behavior today, but the protection is inherited by construction instead of re-earned per key. Adding a command-backed key then can't silently reopen the hole, because there's nothing to forget.
I'd suggest whichever branch lands first carries this, since it costs little now and each additional command-backed surface makes it more expensive.
Two comments in this thread are asking for a different surface
Not a criticism of either — the footer is the only extension point being discussed, so it's where the requests land. But they describe something the footer can't do:
Investigating · 2/5) as work happens.Both want content that appears while the agent is working and goes away when it stops, tied to the turn rather than the session. The footer is the wrong lifetime for that: it's persistent and interval-driven, so expressing "what's happening right now, and how long it's been" means either burning permanent footer space on something transient, or polling for state the footer payload has no reason to carry.
That surface is
StatusIndicatorWidget, and it's tracked separately in #35480 — including the part that doesn't exist here, which is precedence policy: what custom content may render during a turn, and what it must never mask (approvals, permission review, retries, errors, MCP startup, subagent waits). Pointing it out so the footer design doesn't get stretched to cover a lifetime it doesn't fit, and so those use cases land somewhere they can actually be served.The sanitization point above applies to both surfaces, which is why it's here rather than there.
Couldn't agree more. Having control over the density is great:
↻ 5 is the number of compactions in the session.