67d7b14cf54654f4bed3b479c0817cc7441b0c62
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2565d45bb5
|
Act on four reviews: state machine, resource bounds, teardown
Four agents reviewed this in parallel -- correctness, GNOME integration, edge cases, security. Everything below was reproduced before being fixed; several findings that survived the first reading did not survive a probe and are not here. State machine, the two that mattered most. A pending permission prompt was erased by any subagent bookkeeping event: SubagentStop or the next PreToolUse recomputed the state from scratch, so a session sat at "working" with a dialog open and nothing ever raised it again. Blocked now outlives everything except evidence the question was answered. Separately, the stale-event guard refused whole events, including the subagent counter's increments and decrements -- but those are deltas and deltas commute, so a "+1" that lost a timestamp race left the count short and the batch freed the session while a subagent was still running. The guard now gates the state decision only. Corrupt or hostile state files could wedge the panel or take the hook down for every session: a non-numeric pid raised inside sweep_dead before the hook wrote its own file, so one bad byte stopped new sessions appearing at all. Numbers read back from disk are coerced, one unreadable file no longer aborts the sweep, and a stored timestamp far in the future -- corruption, or a clock stepped backwards by NTP -- no longer refuses every later event forever. Resource bounds, all in the compositor process. A state file was read whole with no size check: a symlink to /dev/zero took a test process past 4 GB in three seconds, which in gnome-shell ends the session. Sizes are checked before the read, sessions and zellij subprocesses are capped, labels ellipsize, and cwd and messages are truncated at the hook. Teardown hung off an overridden destroy(), which only runs when JS calls it. An actor destroyed any other way -- another extension rebuilding the panel boxes -- left the timer and the file monitor running against a disposed actor. It is a destroy signal now. The zellij child is killed rather than merely abandoned. The glyph was pinned to physical pixels and rendered half-size on HiDPI; size comes from the stylesheet, and the foreground colour is normalised by inspection rather than assuming which colour struct the shell hands back. Chip labels: non-Latin names all collapsed to "?", because the split treated every Cyrillic letter as a separator -- notable for a tool whose own README is Russian. Seniority also ranked by time-in-state rather than session age, so after a shell restart the older session could take the digit; the hook now records when the session began. zellij: a dump ends with new_tab_template and swap_tiled_layout blocks whose tab lines carry no name, and their panes were being attached to the last real tab -- which then answered for every unmatched directory, confidently and wrongly. install.py no longer widens the mode of a settings.json someone narrowed to 0600, no longer overwrites the pristine .bak on a second run, no longer replaces a symlink out of a dotfiles repository with a regular file, and quotes the hook path. The debug log is capped and README now says plainly that it records prompts verbatim. Not fixed, deliberately: the panel does push the clock about 70 px left with three labelled chips, which is inherent to putting them in the centre box; two different projects abbreviating alike still read as one project with a digit; GNOME 48 remains unverified for the colour struct and for St.BoxLayout's vertical property, both flagged rather than guessed at. |
||
|
|
5398f32826
|
Count subagents, and keep a batch from looking free
Corrected from the previous commit, which had it backwards. When a batch is running the session is working, not waiting: the main agent will pick the results up and consolidate them itself, so sending you to that terminal wastes the trip. That is the common shape of the work here -- ask for a batch, let it run. Simply letting subagent tool calls set "busy" would mostly work and was tempting, but it leaves a hole. Stop fires before the batch finishes, so the session shows as free from the moment the turn ends until the first subagent tool call lands -- and longer whenever the subagents are thinking rather than calling tools. So subagents are counted instead: PreToolUse, matched to ^(Agent|Task)$ +1 SubagentStop -1 UserPromptSubmit reset to 0 While the count is above zero the session cannot read as waiting; Stop and an idle_prompt nudge both leave it working. The session is freed by the last subagent leaving, and only if the main agent has stopped by then. The matcher is anchored because it is a regex: a bare "Task" also matches TaskCreate and friends, which are not subagents. The hook re-checks the tool name itself in case a future matcher behaves differently, and the reset on UserPromptSubmit bounds a count that leaks because a subagent died without its SubagentStop. Measured, not assumed: a matched PreToolUse fires only on agent launches, SubagentStop arrives once per subagent carrying agent_id, and a real three-subagent run walks the count 0-1-2-3-2-0 before Stop frees it. The menu shows the number, as asked. The panel does not: a batch of eight is still one line saying "working 40 min", which is the right line. |
||
|
|
7fc7f63842
|
Show Claude Code session status in the GNOME panel
Answers one question at a glance: is any session waiting for me, and
which one. With several sessions open the cost is not knowing what each
is doing, it is noticing that one stopped an hour ago.
Claude Code hooks write one JSON file per session under
~/.local/state/claude-code-status; the extension watches the directory
with Gio.FileMonitor, so nothing polls and there is no daemon.
Two distinctions carry the design:
* blocked (permission prompt) is kept apart from waiting (turn done).
Merged, a finished task looks as urgent as a stuck one, which is
exactly the judgement the indicator exists to make.
* the panel names the oldest session in the top state, not the latest.
The session you forget is the one that has been waiting longest.
PostToolUse is registered although it looks redundant: it is the only
event that fires after a permission is granted, so without it a session
stays blocked in the panel for the rest of the turn. It writes only on
an actual state change, so the usual case costs no I/O.
Stop and SessionEnd are synchronous, unlike the rest. Both fire as the
process is about to go quiet, and an async hook racing that exit gets
killed before it writes -- claude -p left a session pinned at busy.
Concurrent hooks for one session serialise on an flock plus a timestamp
guard; tests/test-hook.sh covers each separately, because the burst test
passes on the timestamp guard alone.
Sessions running in zellij are located by tab name rather than by path,
matched through dump-layout on the working directory. The dump carries
no pane ids, so ZELLIJ_PANE_ID cannot be used; rows that do not resolve
stay inert instead of pretending a click does something.
lib/sessions.js deliberately imports nothing from the shell resource
namespace, which lets the riskiest logic -- liveness, ordering, partial
reads, monitoring -- run under plain gjs in tests/test-sessions.js.
|