Skip to content

fix: give the statusline channel reading a session dimension (#1362) - #1388

Merged
fdaviddpt merged 3 commits into
mainfrom
fix/1362
Sep 9, 2026
Merged

fix: give the statusline channel reading a session dimension (#1362)#1388
fdaviddpt merged 3 commits into
mainfrom
fix/1362

Conversation

@fdaviddpt

Copy link
Copy Markdown
Contributor

Closes #1362.

Problem

scripts/statusline.py's cache is keyed on the repository alone, and for almost every field (prs, issues, latest, pr_checks, etc.) one shared reading per repository is exactly right. The channel field is not such a fact: raw_state's forwarding vs not_subscribed distinction comes from channel:health's own session: row -- whether the session that took the reading is subscribed to the socket. Two sessions on one repository (one armed via bin/oss-workspace, one a bare claude subscribed to nothing) can hold genuinely different, simultaneously correct answers, and whichever session's reading landed last was rendered as every session's own state -- a measurement taken by a different session, rendered as this session's own, which is this repository's own defect class landing on the instrument whose job is telling unknown from known.

Fix

  • channel_status() gains a fifth cannot_determine reason, other-session, via two new optional kwargs (session, current_session). Checked right after the existing not-asked check and before attribution/staleness -- a reading from a different session is untrustworthy regardless of how sound it otherwise looks.
  • The cache document's channel dict gains a session field, written by refresh() (new optional session_id param) on a fresh reading, threaded from gather()'s own payload.get("session_id") (Claude Code's own statusline JSON payload) through _fork_refresh's new --session-id argv.
  • Every other cached field stays shared exactly as before -- only the channel reading, whose correct answer genuinely differs by session, gained the new dimension. A cache written before this fix carries no session key and self-heals at the next refresh, the same convention statusline: a repo that must declare a short watch_name can never render anything but ch? #754's own attribution migration used.

Self-review finding, fixed in place

The self-review Explore spawn found that a malformed session_id (a non-string value from a malformed payload) would reach subprocess.Popen's argv unchecked and raise an uncaught TypeError, crashing the entire statusline render rather than costing only the channel field its answer. Fixed at both the write site (_fork_refresh only forwards a truthy str) and the read site (gather() coerces a non-string value to None at the source), with a new red/green test. fix_commit_scope.py reported within-scope, so no second review round was needed. The oss:auditor spawn's four-class checklist (absence handling, guard-not-run, untrusted-text boundary, cross-platform) returned clean.

Tests

New file tests/test_statusline_channel_session_1362.py: must-fire/must-not-fire pairs for the other-session reason at both the channel_status() unit level and the gather() integration level, a backward-compat control for a cache with no session key recorded, refresh()/_fork_refresh()/main() argv-threading tests, and the self-review's non-string-session_id regression test. Six pre-existing test files were touched only to widen a monkeypatched _fork_refresh lambda's signature to accept the new optional third parameter.

Not pushed, not opened as a PR by this session -- both commits are on fix/1362, ready for the maintainer to push and open.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QYm4rkixzFWu3MXm2r9hhG

[AI-generated]

fdaviddpt and others added 3 commits September 9, 2026 21:55
Two sessions on one repository -- one armed via `bin/oss-workspace`, one a
bare `claude` subscribed to nothing -- can hold genuinely different,
simultaneously correct channel readings, and the cache was keyed on the
repository alone. Whichever session's reading landed last was rendered as
every session's own state.

`channel_status` gains a fifth `cannot_determine` reason, `other-session`,
checked right after `not-asked` and before attribution or staleness. The
cache document's `channel` dict gains a `session` field, threaded from
`gather()`'s own `payload.get("session_id")` through `_fork_refresh`'s new
`--session-id` argv into `refresh()`. Every other cached field stays shared
exactly as before -- only the channel reading gains the new dimension. A
cache written before this fix carries no `session` key and self-heals at the
next refresh, the same convention #754's own `attribution` migration used.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYm4rkixzFWu3MXm2r9hhG
…review)

The Explore self-review spawn found that `_fork_refresh` forwarded
`session_id` straight into `subprocess.Popen`'s argv with no type check, and
the surrounding `except (OSError, ValueError)` does not catch the
`TypeError` a non-str/bytes/PathLike argv element raises -- so a malformed
statusline payload (`session_id` arriving as an int or a dict) would crash
the ENTIRE status line render, not just the channel field it was meant to
help.

Guarded at both the write site (`_fork_refresh` only forwards a truthy
`str`) and the read site (`gather()` coerces a non-string `session_id` to
`None` before it reaches either `_fork_refresh` or `channel_status`), the
same "untrusted value becomes absent, never a crash" treatment this module
already gives a malformed `.supertool.json`. New test:
`test_fork_refresh_a_non_string_session_id_does_not_crash_the_whole_render`,
red before this commit, green after.

`fix_commit_scope.py --file scripts/statusline.py --file
tests/test_statusline_channel_session_1362.py` -> within-scope, no
second review pass needed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYm4rkixzFWu3MXm2r9hhG
… own fix

CI (PR #1388, macos-latest/3.12 and windows-latest/3.12) failed with
`TypeError: <lambda>() got an unexpected keyword argument 'session_id'` at
`main()`'s `refresh(root, session_id=session_id)` call. The earlier commits
in this branch widened every monkeypatched `_fork_refresh` lambda (six
files) but missed two sites that monkeypatch `refresh` itself:

- tests/test_statusline_mark_stale_receipt_1346.py::test_refresh_root_as_last_argument_does_not_raise
- tests/test_triage_board_stale_1313.py::test_refresh_alone_is_not_the_same_event_and_does_not_write_stale_after

Both widened to accept and ignore the new optional `session_id` parameter,
the same shape as the other six. Grepped tests/ once more for every
remaining `monkeypatch.setattr(statusline, "refresh"|"_fork_refresh", ...)`
call site -- none left uncovered. Full targeted set (11 files) plus these
two: 244 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYm4rkixzFWu3MXm2r9hhG
@fdaviddpt
fdaviddpt merged commit 79e7236 into main Sep 9, 2026
10 checks passed
@fdaviddpt
fdaviddpt deleted the fix/1362 branch September 9, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant