Skip to content

cc-status shim: a stale background task no longer pins a tab at working - #96

Merged
hadees merged 6 commits into
mainfrom
feat/cc-status-shim
Oct 8, 2026
Merged

hadees merged 6 commits into
mainfrom
feat/cc-status-shim

Conversation

@hadees

@hadees hadees commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

What

iTerm2 3.7's cc-status keeps a Session Status row at "working" whenever the Stop hook payload's background_tasks array 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-field it2 set-status paints 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 on Notification(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 explain prints the decision for a payload; cc-status settings prints the hook block.
  • Deployed named 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). it2 is resolved beside the real binary, since PATH only carries it under an experimental iTerm2 setting.
  • verify.sh gains a utilstr verb and the manifest records the cc-status contract the shim rests on (payload keys, the --background-tasks flag, 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

  • The hook rewiring in each profile's settings.json (overlay-owned): point the command at the bare absolute path cc-status settings prints, after this merges and applies.
  • Per-seat colours, tabstatus() full-field paints, doctor lines: follow-up, per the plan.
  • The iTerm2 skill is still stamped 3.6.11 against an installed 3.7.3, and one recorded fact has already drifted (the "API key … stored securely in the Keychain" sentence is gone from 3.7.3). verify.sh reports 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; live cc-status explain against a real session's task files classifies as expected.

🤖 Generated with Claude Code

hadees and others added 2 commits October 6, 2026 14:09
…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>
@hadees hadees self-assigned this Oct 6, 2026
hadees and others added 2 commits October 6, 2026 14:20
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
hadees force-pushed the feat/cc-status-shim branch from 71e54df to be55738 Compare October 6, 2026 19:47
…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>
@hadees
hadees merged commit ab937cc into main Oct 8, 2026
5 checks passed
@hadees
hadees deleted the feat/cc-status-shim branch October 8, 2026 01:19
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