Repository navigation
cc-status shim: a stale background task no longer pins a tab at working - #96
Merged
Merged
Conversation
…a tab at working iTerm2 3.7's cc-status keeps a Session Status row at "working" whenever the Stop payload's background_tasks array is non-empty, and Claude Code never ages that registry out: a backgrounded shell blocked on stdin kept a tab "working" for 41 hours, a subagent that never reported did the same with no process behind it. The shim takes cc-status's place in each profile's hook command and changes that one case: cc-status gets the Stop payload with the array emptied and paints idle, then one it2 set-status call puts what is still registered in the detail, keeps the stored count truthful, and turns the dot blue. Every other event passes through untouched. Fails open. Deployed as ~/.local/libexec/cc-status-shim/cc-status: iTerm2 3.7.3's health check accepts a hook only if its command is a bare executable path ending in /cc-status, so the shim keeps the name, off PATH. it2 is taken from beside the real binary, where iTerm2 ships it; PATH carries it only while an experimental iTerm2 setting is on. macOS-only, like worktabs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A new utilstr verb checks a string inside one bundled utility, the way binstr checks the main executable. The manifest now records what the shim rests on: the payload keys cc-status reads, the it2 flag it stores the task count under, its detail text and colours, and the app's own symlink upkeep. Strings of 15 bytes or fewer live inline in a Swift binary, so only longer literals are recorded. Not grep -q under pipefail: its early exit hands strings(1) a SIGPIPE on a binary still printing, failing a fact that is present. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The first cut treated every background task at Stop as stale: it emptied the array and stored the task count back into iTerm2. That hides real work (a Stop while a background subagent is mid-edit read as idle) and the stored count undid itself: cc-status's Notification(idle_prompt) handler, a minute after Stop, reads the count back with get-background-tasks and repaints "working · N background tasks running". Now each task is classified by its output file under the session's tasks/ directory (the sibling of scratchpad_dir; for an agent a symlink to its transcript). Written within tabstatus.quiet-minutes (default 15) is active; older, or a dangling symlink, is quiet; no file to judge by counts as active, since wrongly active only delays the idle. cc-status is fed only the active tasks, with the id and status keys its older schema reads. When nothing is active but something is quiet, one it2 call paints the full field set: idle, amber (blue is cc-status's waiting colour), the quiet tasks in the detail, and a stored count of 0, never the quiet count. StopFailure and SubagentStop are handled like Stop, and the stopping agent is dropped from its own SubagentStop. Adds `cc-status explain` (prints each verdict, calls nothing) and `cc-status settings` (the hooks block for a profile's settings.json, the ten iTerm2 events plus SubagentStart, with the home path expanded). Drops the off-mac chezmoi ignore: the hook command is a bare path, so the file must exist wherever a settings.json names it, and the shim already exits 0 without iTerm2's binary. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Under set -u, dash rejects ${ITERM_SESSION_ID#*:} when the variable is
unset ("parameter not set", exit 2), where macOS's bash lets it through.
Ubuntu's /bin/sh is dash, so the "no session id" pass-through failed
there. Default the variable first, then strip.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
wc -m counts the three-byte ellipsis as three characters under a C locale, which is what the Rocky CI container runs, and the detail's "·" makes a byte count wrong everywhere. python counts code points the same way on every runner. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
hadees
force-pushed
the
feat/cc-status-shim
branch
from
October 6, 2026 19:47
71e54df to
be55738
Compare
…tion proxies for seats that cannot Three rules for every profile, under "Talking to other Claude Code sessions". When the operator names a repo this session has no access to, the answer "I lack access" is wrong when a Claude Code session is running there: the request is to find that session and put the question to it, and the mechanics of how (ListAgents, postbox) are already written down below. plan-fable and the other seats have no messaging tools at all, so anything the operator wants carried to or from one is the main conversation's to relay, not a limitation to report. And "postbox" in the operator's mouth means session messaging in general, whichever transport the rules pick; while it works, replies say what was sent and what came back, not which tool carried it. Co-Authored-By: Claude Fable 5.1 <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.
What
iTerm2 3.7's
cc-statuskeeps a Session Status row at "working" whenever the Stop hook payload'sbackground_tasksarray is non-empty, and Claude Code never ages that registry out. Measured today: a backgrounded shell blocked on stdin kept a tab "working" for 41 hours; a subagent that never reported did the same with no process behind it.dot_local/libexec/cc-status-shim/executable_cc-status— takes cc-status's place in each profile's hook command. On Stop, StopFailure and SubagentStop it classifies each background task as active or quiet by the age of Claude Code's own per-task output file (<session>/tasks/<task_id>.output; default quiet after 15 min,tabstatus.quiet-minutes). cc-status is fed only the active ones, so a Stop over a running background agent still paints "working" (what iTerm2 3.7.2 intended). When nothing is active, one full-fieldit2 set-statuspaints the row idle in amber (tabstatus.color.quiet, default#af8700) with the quiet tasks in the detail and stores 0 — cc-status re-reads the stored count onNotification(idle_prompt)a minute later and would otherwise repaint "working". A task with no file to judge by counts as active. Every other event passes through byte for byte. Fails open everywhere.cc-status explainprints the decision for a payload;cc-status settingsprints the hook block.cc-status, off PATH: iTerm2 3.7.3's health check accepts a hook only if its command is a bare executable path ending in/cc-status(verified in the onboarding source at that tag).it2is resolved beside the real binary, since PATH only carries it under an experimental iTerm2 setting.verify.shgains autilstrverb and the manifest records the cc-status contract the shim rests on (payload keys, the--background-tasksflag, detail text, colours, symlink upkeep), so a point release that moves them shows up. All 12 new facts hold on the installed 3.7.3.Not in this PR
settings.json(overlay-owned): point the command at the bare absolute pathcc-status settingsprints, after this merges and applies.tabstatus()full-field paints, doctor lines: follow-up, per the plan.verify.shreports both; neither is new here.Tests
tests/cc-status-shim.bats(23, stub cc-status and it2 side by side, fixture task files with set mtimes),tests/iterm2-doctor.bats(utilstr pass and vanished-string cases),tests/chezmoi.bats. 50/50 locally; livecc-status explainagainst a real session's task files classifies as expected.🤖 Generated with Claude Code