Skip to content

feat(statusline): persist a verbatim per-session JSON snapshot for external readers - #45

Merged
cdeust merged 1 commit into
mainfrom
feat/statusline-session-snapshot
Sep 26, 2026
Merged

cdeust merged 1 commit into
mainfrom
feat/statusline-session-snapshot

Conversation

@cdeust

@cdeust cdeust commented Sep 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • state/sessions/<session_id>.snapshot.json now 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 read 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 renderer itself.
  • write_session_snapshot (plugins/statusline/assets/lib/session_state.sh) writes only when session_id is 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, then mv), 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.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 (the "What ends up on disk" tree plus a dedicated section, one language per file).
  • statusline bumped 2.2.0 → 2.3.0 (plugin.json, marketplace.json entry, 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.sh sourced against an unmodified copy of origin/main's statusline-command.sh/lib/:

tests/statusline/test_session_snapshot.sh: ligne 62: write_session_snapshot: commande introuvable
FAIL: test_write_session_snapshot_second_write_replaces_the_first
...
FAIL: test_write_session_snapshot_unwritable_dir_is_silent
FAIL: test_renderer_writes_the_snapshot_and_still_renders
Total: 9 — Echecs: 5

Same suite against this branch:

Total: 9 — Echecs: 0

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 (includes tests/test_statusline_layout.py, which exercises the real install + renderer under a temp HOME): 125 passed
  • ruff check plugins tests tools: all checks passed
  • ruff format --check plugins tests tools: 36 files already formatted
  • tools/check-lock-drift.py requirements-dev.in requirements-dev.txt: matches
  • shellcheck 0.11.0 (pinned, matching CI) across statusline-command.sh, lib/*.sh, costs.sh, install.sh, tools/*.sh, tests/statusline/*.sh: clean
  • bash tests/statusline/test_heat_rgb.sh: 22/22
  • bash tests/statusline/test_fit_and_pace.sh: 53/53
  • bash tests/statusline/test_session_snapshot.sh: 9/9
  • python -m json.tool over every plugin/marketplace/hooks/config JSON file CI validates: all valid

Snapshot 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 other sessions/ files (this repo's .main/.sub caches share the same directory and id-prefixed naming convention already relied on by an external session-purge tool's statusline/state/sessions/{sid}.* glob).

Test plan

  • tests/statusline/test_session_snapshot.sh red on origin/main, green on this branch
  • Full pytest suite green
  • ruff check + format green
  • shellcheck 0.11.0 clean across the full CI file set
  • Existing statusline bash suites unaffected

🤖 Generated with Claude Code

…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>
@cdeust
cdeust merged commit 2d534c8 into main Sep 26, 2026
3 checks passed
@cdeust
cdeust deleted the feat/statusline-session-snapshot branch September 26, 2026 16:30
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