Skip to content

statusline: show the Shift+Tab permission mode - #2

Draft
broisnischal wants to merge 2 commits into
mainfrom
worktree-statusline-permission-mode
Draft

broisnischal wants to merge 2 commits into
mainfrom
worktree-statusline-permission-mode

Conversation

@broisnischal

@broisnischal broisnischal commented Aug 3, 2026 •

Copy link
Copy Markdown
Owner

Why

The status line showed no permission mode. Root cause: the statusLine payload does not carry one. permission_mode is absent from the documented schema and from a live capture on Claude Code 2.1.220 — so nothing on stdin exposes it.

(Same reason claude-sudo's statusline renderer reads .permission_mode and its [SUDO ✗] branch can never fire — not fixed here, noted for a follow-up in that repo.)

What

The transcript is a live source: Claude Code appends a {"type":"permission-mode","permissionMode":"…"} entry the moment the mode changes, so the badge follows Shift+Tab immediately.

Reading the transcript every render would break this file's "must stay cheap" contract, so the resolved mode is cached against transcript size:

case cost
transcript unchanged one wc -c — fstat, ~1 ms on a multi-MB file
transcript grew scan only the appended bytes
no cache / rotated bootstrap from the last 1 MiB, not the whole file

A full scan of a real 7.9 MB transcript measured 231 ms; bounded to 1 MiB it is 66 ms, one time per session. Warm renders are +5 ms over baseline (21 ms vs 16 ms).

Rendering:

mode badge
default (nothing — Claude Code shows no indicator for it either)
plan [⏸ plan] blue
acceptEdits [⏵ accept edits] green
auto [⏵⏵ auto] yellow
bypassPermissions [⏵⏵ bypass] red
dontAsk [⏵⏵ dont ask] red

PHOBOS_STATUSLINE_MODE=0 opts out.

Testing

bash -n and shellcheck -S warning clean. 16/16 cases pass: all six modes, missing/empty/mode-less transcript, bootstrap, growth with mode retained, switch detection, rotation rescan, opt-out, and an entry pushed beyond the 1 MiB window (degrades to no badge, recovers on the next emit). Verified against a real captured payload — renders [⏵⏵ auto].

Care taken with set -euo pipefail: every test-in-a-list is an if, not &&, and the grep pipeline that legitimately fails when no match is guarded.

The status line had no permission-mode indicator, because the statusLine
payload does not carry one — `permission_mode` is absent from the documented
schema (code.claude.com/docs/en/statusline), so nothing on stdin exposes it.

The transcript does: Claude Code appends a `permission-mode` entry the moment
the mode changes, so the badge tracks Shift+Tab live.

Reading the transcript on every render would break this file's cheapness
contract, so the resolved mode is cached against the transcript size:

  - transcript unchanged -> one `wc -c` (fstat, ~1ms on a multi-MB file)
  - transcript grew      -> scan only the appended bytes
  - no cache / rotated   -> bootstrap from the last 1MiB, not the whole file
    (a full scan of a 7.9MB transcript measured 231ms; bounded it is 66ms, and
    the entry is re-emitted every turn so an older one is superseded shortly)

`default` renders nothing, matching Claude Code showing no indicator for it.
Set PHOBOS_STATUSLINE_MODE=0 to opt out.
@changeset-bot

changeset-bot Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 69b61f3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

An always-on `[⏵⏵ auto]` is noise: with permissions.defaultMode=auto you are in
auto essentially always, and Claude Code already prints its own `⏵⏵ auto mode
on` indicator right below the prompt. The badge only earns its space once you
have switched away from the mode you configured.

Resolve permissions.defaultMode from settings (local > project > user, with a
PHOBOS_STATUSLINE_MODE_DEFAULT override) and render nothing while the current
mode matches it. Resolved once per session and cached alongside the mode:
defaultMode applies at startup, so editing it mid-session cannot change the
mode the session is actually in — caching is correct here, not just cheap.

Cache is now `|`-delimited. Space is IFS whitespace, so bash collapses runs of
it and an empty permission-mode field would have silently shifted defmode into
its place. Legacy space-delimited cache files fail the numeric size check and
trigger one harmless re-bootstrap.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant