feat(statusline): persist a verbatim per-session JSON snapshot for external readers - #45
Merged
Merged
Conversation
…ternal readers
state/sessions/<session_id>.snapshot.json now holds the exact statusLine
stdin payload from the renderer's last refresh, byte for byte, so a reader
outside this renderer (a Stream Deck plugin, a second dashboard) can show
context_window.used_percentage, rate_limits.five_hour/seven_day,
cost.total_cost_usd, model.display_name, etc. without re-deriving anything
or invoking the script itself.
write_session_snapshot (lib/session_state.sh) writes only when session_id is
non-empty and matches [A-Za-z0-9_-]+ (rejects any id carrying "/" or ".." —
every Claude Code session id, a UUID, qualifies), atomically via a
same-directory temp file + mv, and never blocks or slows the render on a bad
id or a disk error — the same failure-tolerant convention every other reader
in this module already follows. statusline-command.sh now captures stdin
with trailing bytes preserved (`input=$(cat)` alone strips trailing
newlines) so "verbatim" is literal, verified byte-for-byte including a
trailing-newline case.
Documented in README.md/README.fr.md ("What ends up on disk" + a dedicated
section); statusline bumped 2.2.0 -> 2.3.0 (plugin.json, marketplace.json,
CHANGELOG.md), mirroring how context-guard 2.1.0 was bumped in-PR earlier in
this same file (52fe593).
Tests: tests/statusline/test_session_snapshot.sh (byte identity, trailing
newline preservation, atomicity — no leftover temp fragment, overwrite on
re-render, path-traversal and empty-id rejection, unwritable-dir silence,
and that a write failure never changes the renderer's stdout), red against
origin/main (function undefined), green after. Existing suites
(test_heat_rgb.sh, test_fit_and_pace.sh, tests/test_statusline_layout.py,
full pytest, ruff check/format, shellcheck 0.11.0 across the full CI sweep)
all still pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
state/sessions/<session_id>.snapshot.jsonnow holds the exact statusLine stdin JSON from the renderer's last refresh, byte for byte, so an external reader (a Stream Deck plugin, a second dashboard) can readcontext_window.used_percentage,rate_limits.five_hour/seven_day,cost.total_cost_usd,model.display_name, etc. without re-deriving anything or invoking the renderer itself.write_session_snapshot(plugins/statusline/assets/lib/session_state.sh) writes only whensession_idis non-empty and matches[A-Za-z0-9_-]+— rejecting any id carrying/or..(every Claude Code session id, a UUID, qualifies) — atomically (same-directory temp file, thenmv), and never blocks or slows the render on a bad id or a disk error, mirroring the failure-tolerant convention every other reader in that module already follows.statusline-command.shnow captures stdin with trailing bytes preserved (input=$(cat)alone strips trailing newlines), so "verbatim" is literal — verified byte-for-byte including a trailing-newline case.README.md/README.fr.md(the "What ends up on disk" tree plus a dedicated section, one language per file).plugin.json,marketplace.jsonentry,CHANGELOG.md), mirroring how context-guard 2.1.0 was bumped in-PR in 52fe593 — the repo's convention is to bump the touched plugin's own version inside the feature PR, not in a separate release commit.Gate (red before, green after)
tests/statusline/test_session_snapshot.shsourced against an unmodified copy oforigin/main'sstatusline-command.sh/lib/:Same suite against this branch:
Covers: byte identity, trailing-newline preservation, atomicity (no leftover
.tmp.*fragment), overwrite on re-render, path-traversal (../,a/b,..,/etc/passwd) and empty-id rejection (no file written anywhere under the temp HOME), an unwritable state dir failing silently (exit 0, no side effect outside the sandbox), and — through the real renderer as a subprocess — that a write failure never changes the renderer's stdout.Full CI-equivalent run (this branch, local)
pytest -q(includestests/test_statusline_layout.py, which exercises the real install + renderer under a temp HOME): 125 passedruff check plugins tests tools: all checks passedruff format --check plugins tests tools: 36 files already formattedtools/check-lock-drift.py requirements-dev.in requirements-dev.txt: matchesshellcheck 0.11.0(pinned, matching CI) acrossstatusline-command.sh,lib/*.sh,costs.sh,install.sh,tools/*.sh,tests/statusline/*.sh: cleanbash tests/statusline/test_heat_rgb.sh: 22/22bash tests/statusline/test_fit_and_pace.sh: 53/53bash tests/statusline/test_session_snapshot.sh: 9/9python -m json.toolover every plugin/marketplace/hooks/config JSON file CI validates: all validSnapshot path/format
${STATUSLINE_STATE_DIR}/sessions/<session_id>.snapshot.json(default~/.claude/statusline/state/sessions/) — the exact bytes read from stdin this refresh, no reshaping. Removed alongside the session's othersessions/files (this repo's.main/.subcaches share the same directory and id-prefixed naming convention already relied on by an external session-purge tool'sstatusline/state/sessions/{sid}.*glob).Test plan
tests/statusline/test_session_snapshot.shred onorigin/main, green on this branch🤖 Generated with Claude Code