Skip to content

feat(cli): volunteer unwired status line via stderr hint (#188) - #197

Merged
luongnv89 merged 6 commits into
mainfrom
feat/188-context-stats-never-volunteers-that-the
Sep 2, 2026
Merged

luongnv89 merged 6 commits into
mainfrom
feat/188-context-stats-never-volunteers-that-the

Conversation

@luongnv89

@luongnv89 luongnv89 commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Closes #188

Summary

The context-stats CLI now volunteers — on its own, once per invocation — that the Claude Code status line is installed but unwired: when ~/.claude/settings.json carries no effective statusLine block, every context-stats command prints a single line to stderr (! statusLine is not wired into ~/.claude/settings.json — the status line will never run. Fix: context-stats doctor --fix). The hint is stderr-only, never raises, never changes the exit code, and leaves graph/export/report stdout byte-identical. It is suppressed when the wiring is healthy, or on demand via a new suppress_setup_hint config key and the CONTEXT_STATS_SUPPRESS_SETUP_HINT env var. This closes the #186 residual gap for upgraders, who never re-read install docs and had no signal that the status line would never run.

Approach

Option 2 — Balanced: extract the wiring predicate, hook it into the CLI, mirror the config key across both copies.

The statusLine check doctor already performs was extracted into a cheap, subprocess-free _effective_statusline() predicate in cli/doctor.py (check_settings now delegates to it — one source of truth for "is the status line wired?"). The CLI entry main() in cli/context_stats.py calls _maybe_warn_setup_hint() between parse_args() and action dispatch: lazy-imports the predicate, suppresses on the suppress_setup_hint key or env var, and writes exactly one stderr line when unwired. Per the CLAUDE.md sync-point parity contract, the key is mirrored into the standalone read_config() and both conf templates (kept byte-identical by test_config_colors.py).

Decision Record

  • Root cause: The statusLine wiring exists only in install.sh's jq-gated block, which every pip/uv install and every upgrade skips; wheels cannot run post-install hooks; claude-statusline only runs once wiring already exists. The one process guaranteed to run while unwired is the context-stats CLI, whose every invocation was silent — so the installed-but-inactive state was never volunteered, and doctor only helped users who already knew to run it.
  • Options considered: Option 1 — Minimal: CLI-only hook with documented parity carve-out; Option 2 — Balanced: mirror the key across both copies; Option 3 — Comprehensive: mirror key + install.sh upgrade-path repair + env-var parity.
  • Options rejected: Option 1 — creates a deliberate renderer-invisible asymmetry between Config and the standalone read_config that the parity contract pushes against, costing more in carve-out documentation than the mirror saves; Option 3 — overbuilds beyond AC#1–6: touching install.sh/check-install.sh/README widens the blast radius with an untested shell surface while the startup hook already puts the message on the one process guaranteed to run while unwired.
  • Selected option: Option 2 — Balanced (mirror the key across both copies)
  • Residual risk: none identified. Known accepted caveat (pre-existing, sub-threshold): when a conf file exists with malformed values, the hint's Config.load() can emit a [statusline] warning: stderr line on commands that never otherwise loaded config — never raises, never changes exit code.
  • Reproduction: n/a — feature, not a bug.

Analyzed at: feat/188-context-stats-never-volunteers-that-the @ 6c613cd (2026-09-02)

Changes

File Change
src/claude_statusline/cli/doctor.py Extracted _effective_statusline() (pure file-reads, never raises) from the effective-block resolution; check_settings delegates to it
src/claude_statusline/cli/context_stats.py _setup_hint_suppressed() (env var + read-only conf key check), _maybe_warn_setup_hint(args) (stderr-only, never-raise, help-guarded), called in main() between parse_args() and dispatch
src/claude_statusline/core/config.py suppress_setup_hint: bool field + tolerant parse + to_dict + _MINIMAL_CONFIG_FALLBACK
scripts/statusline.py Mirrored key into read_config() defaults + _BOOL_CONFIG_KEYS (renderer parses, never branches)
src/claude_statusline/data/statusline.conf.default + examples/statusline.conf Identical suppress_setup_hint comment block (byte-identical pair)
docs/configuration.md Documented the key + CONTEXT_STATS_SUPPRESS_SETUP_HINT env var
docs/troubleshooting.md New "statusLine is not wired" section: behavior + both suppression methods
tests/python/test_cli_entry.py TestSetupHint: 23 tests (unwired→hint, wired→silent, missing/malformed→silent, suppression via key + env, no hint on help/version, byte-identical stdout per action, no conf-file side effect)
tests/python/test_doctor.py TestEffectiveStatusline: 7 predicate tests on the fake_home fixture
tests/python/test_parity.py Registered suppress_setup_hint in the config-parsing parity set

Test Results

  • Unit tests: 1572 passed (1 pre-existing POSIX-mode skip)
  • Integration tests: n/a (no framework)
  • E2e tests: n/a (no framework)
  • Build: n/a — pure Python; ruff check . and mypy src scripts clean
  • Coverage: 96.22% (floor 94%)
  • QA cycles: 2

Acceptance Criteria Verification

Criterion Status Evidence
Unwired → one-line hint on stderr on any invocation pass test_hint_shows_when_unwired etc. — _maybe_warn_setup_hint() writes exactly one line to sys.stderr; exercised for graph/export/report/sessions/doctor
Wired → no hint pass test_no_hint_when_wired_effective_statusline() returns a block → silent
stdout byte-identical for graph/export/report pass capsys asserts out unchanged while hint appears on err (graph/export/report no-hint + hint cases); existing golden/export/report tests still green
Never raises / never changes exit code incl. missing/unreadable/malformed settings.json pass test_*_missing* / test_*_malformed* / test_*_unreadable* — silent + exit 0; broken HOME never raises
Config key + env var suppress, documented pass test_suppressed_by_conf / test_suppressed_by_env; docs/configuration.md + docs/troubleshooting.md updated
Tests cover unwired→hint, wired→silent, malformed→silent+no crash, suppression pass TestSetupHint (23) + TestEffectiveStatusline (7) cover the full matrix

@luongnv89
luongnv89 merged commit 4bd661d into main Sep 2, 2026
35 checks passed
@luongnv89
luongnv89 deleted the feat/188-context-stats-never-volunteers-that-the branch September 2, 2026 20:48
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.

context-stats never volunteers that the status line is unwired — doctor only helps users who already know to run it

1 participant