Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4a8e111
fix(coord): overlap gave two different answers the same bytes, twice
wshallwshall Aug 2, 2026
c6b8f42
fix(coord): the collision gate reported an all-clear when it had chec…
wshallwshall Aug 2, 2026
07deb2f
fix(coord): claim.ps1 accepted a new note, reported success, and disc…
wshallwshall Aug 2, 2026
3bacb6a
docs(coord): record the three fixes, and correct a claim that has exp…
wshallwshall Aug 2, 2026
2ac05f4
Merge remote-tracking branch 'origin/main' into claude/announce-hook-…
wshallwshall Aug 2, 2026
c8f0030
docs(coord): announce-on-join merged and was never installed
wshallwshall Aug 2, 2026
6f5495b
Merge remote-tracking branch 'origin/main' into claude/announce-hook-…
wshallwshall Aug 2, 2026
81e11d8
fix(coord): five defects this PR's own first pass introduced or left
wshallwshall Aug 2, 2026
38505f3
docs(worktrees): "is it live yet" has two answers, and they are diffe…
wshallwshall Aug 2, 2026
ad64b90
Merge remote-tracking branch 'origin/main' into claude/announce-hook-…
wshallwshall Aug 2, 2026
bc0f684
docs(worktrees): the freeze bullet had the right lesson and the wrong…
wshallwshall Aug 2, 2026
de868f5
docs(worktrees): put the two numbers behind the freeze bullet, with t…
wshallwshall Aug 2, 2026
8555fb4
docs(ledger): the CI backstop does not re-check ownership, and said i…
wshallwshall Aug 2, 2026
f3699b9
Merge branch 'main' into claude/announce-hook-intersession-d11524
wshallwshall Aug 2, 2026
376a9a0
Merge branch 'main' into claude/announce-hook-intersession-d11524
wshallwshall Aug 2, 2026
3f1bba5
Merge branch 'main' into claude/announce-hook-intersession-d11524
wshallwshall Aug 2, 2026
f124b93
feat(coord): publish the account's plan limits so a session knows bef…
wshallwshall Aug 2, 2026
6b8a5f4
Merge remote-tracking branch 'origin/main' into claude/announce-hook-…
wshallwshall Aug 2, 2026
e416bfc
Merge branch 'main' into claude/announce-hook-intersession-d11524
wshallwshall Aug 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions docs/WORKTREES.md
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,58 @@ pwsh -NoProfile -File scripts\hooks\collision_gate.ps1 -PathOverride docs\BACKLO
Empty output means no live session holds it. Documented in-script as a test affordance; surfaced here
because a session that needed the answer found it by reading the source.

## Account usage — knowing before a session is cut off

**What it fixes.** Sessions were hitting the plan limit mid-task and losing work. The account's real
quota state exists — Settings > Usage shows it — but it is not visible from inside a session, so nobody
knew how much headroom was left until it ran out.

**The one place the numbers arrive.** Claude Code hands `rate_limits` to a **statusLine command's stdin
and nowhere else**. Not `SessionStart`, not `UserPromptSubmit`, not `Stop` — the payloads were enumerated
in the shipped binary and it appears in exactly one of them. So quota state cannot be subscribed to; it
has to be *collected* by a statusLine and published somewhere shared. That single fact determines the
whole shape:

| | |
|---|---|
| [`usage-collect.ps1`](../scripts/coord/usage-collect.ps1) | the statusLine. Publishes to `~/.claude/mefor-usage/latest.json` |
| [`usage.ps1`](../scripts/coord/usage.ps1) | reads it, adds burn rate, answers *will this run out before it resets* |
| [`install-usage-statusline.ps1`](../scripts/coord/install-usage-statusline.ps1) | wires it (owner, plain terminal) |

**One publisher, N readers.** The quota is **account-wide** — every session in every repo draws down the
same 5-hour and 7-day pools — so any one session's reading is the truth for all of them. Do not run a
collector per session expecting to sum them; that double-counts a shared pool. The publish path is
user-level for the same reason: the data is a property of the account, not of a checkout.

**It only runs in an interactive session.** The statusLine is part of the TUI's render tree and never
executes under `claude -p` or the SDK. A headless coordinator can *read* what this publishes and can
never publish it itself. `refreshInterval` is set because statusLine updates are event-driven and go
silent when a session is idle — Anthropic's docs name *"a coordinator waits on background subagents"* as
exactly the case where that leaves you blind.

**Two of the four Settings > Usage numbers are not available at all.** The payload carries `five_hour`
and `seven_day` only. The **per-model weekly buckets** (the Fable/Opus/Sonnet bars) and the **plan tier**
are absent, and the request to expose them was closed as not-planned. `usage.ps1` prints that on every
run rather than burying it: if Opus is being burned hard across many sessions, the bucket most likely to
stop you is the one nothing here can see. Two green bars and an invisible third is worse than no tool.

```powershell
pwsh -NoProfile -File scripts\coord\usage.ps1 # human
pwsh -NoProfile -File scripts\coord\usage.ps1 -Json # coordinator
```

Exit codes so a coordinator can branch without parsing prose: **0** ok, **10** warn, **11** critical,
**20** unknown. `UNKNOWN` is a real answer here and is returned whenever the reading is stale, undateable
or future-dated — a percentage is never extrapolated from a dead publisher, and every number is printed
with its own age. **Do not read a missing bucket as an empty one.**

> **`ccusage` does not do this**, despite being the tool everyone recommends and despite several
> summaries claiming it "fetches real rate limit data". It parses transcripts for tokens and dollars; its
> "5-hour block" is a client-side reconstruction and its statusline percentage is context-window.
> `claude-usage-tracker` is the same mistake in cruder form — real token parsing compared against a
> hardcoded limit table. Anything reading plan state from either is confidently wrong at exactly the
> moment it matters. Tokens and plan-limit consumption are different quantities.

## Announcing yourself (UserPromptSubmit hook)

**What it fixes.** Everything above is **pull**-based: a new session discovers its peers and the peers
Expand Down
136 changes: 136 additions & 0 deletions scripts/coord/install-usage-statusline.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<#
.SYNOPSIS
Wire usage-collect.ps1 as the Claude Code statusLine, so the account's plan limits get published.

.DESCRIPTION
Run this ONCE, from a plain terminal. It writes `statusLine` into the USER-level
~/.claude/settings.json, so every session on this machine publishes -- and reads -- the same
account-wide quota state. See usage-collect.ps1 for why the statusLine is the only source.

IT TAKES EFFECT IN NEWLY STARTED SESSIONS. Existing sessions keep the config they booted with, the
same as the coordination hooks. And it only ever runs in an INTERACTIVE session: the statusLine is
part of the TUI's render tree and never executes under `claude -p` or the SDK, so a headless
coordinator can read what this publishes but can never publish it itself.

WHY IT POINTS AT AN ABSOLUTE PATH rather than resolving the repo per invocation: the statusLine runs
on every assistant message behind a 300ms debounce, and a `git rev-parse` per fire is latency on the
render path for a value that never changes. The trade is that moving or deleting the checkout breaks
it -- so the wired command TESTS FOR THE SCRIPT and degrades to a quiet marker instead of erroring
into the status bar on every message.

refreshInterval is set because statusLine updates are EVENT-DRIVEN -- a new assistant message,
/compact, a permission-mode change -- and go silent when a session is idle. Anthropic's own docs
name "a coordinator waits on background subagents" as the case where that leaves you blind, which is
exactly this repo's situation.

.EXAMPLE
pwsh -NoProfile -File scripts\coord\install-usage-statusline.ps1
pwsh -NoProfile -File scripts\coord\install-usage-statusline.ps1 -Status
pwsh -NoProfile -File scripts\coord\install-usage-statusline.ps1 -Uninstall
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[switch]$Uninstall,
[switch]$Status,
# Milliseconds. Minimum honoured by Claude Code is 1000.
[int]$RefreshInterval = 10000,
[string]$SettingsPath = (Join-Path $env:USERPROFILE ".claude\settings.json"),
# Which collector to wire. Defaults to the PRIMARY checkout's copy, deliberately: a worktree is
# disposable and a user-level statusLine pointing into one dies with it. Overridable so tests can
# drive the real installer against a fixture instead of asserting a copy of its rules.
[string]$CollectorPath
)

$ErrorActionPreference = "Stop"
$MARKER = "mefor-usage"

# The primary checkout, not this worktree: a worktree is disposable and the statusLine outlives it.
$common = (& git rev-parse --path-format=absolute --git-common-dir 2>$null)
if ($LASTEXITCODE -ne 0 -or -not $common) { throw "Not inside a git repository -- run this from the MessageFoundry checkout." }
$primary = Split-Path ($common.Trim()) -Parent
$script = if ($CollectorPath) { $CollectorPath } else { Join-Path $primary "scripts/coord/usage-collect.ps1" }

function Get-Settings {
if (-not (Test-Path -LiteralPath $SettingsPath)) { return [ordered]@{} }
$raw = Get-Content -LiteralPath $SettingsPath -Raw
if (-not $raw.Trim()) { return [ordered]@{} }
return ($raw | ConvertFrom-Json -AsHashtable)
}

if ($Status) {
$s = Get-Settings
$sl = $s['statusLine']
Write-Host ""
if (-not $sl) { Write-Host "statusLine: NOT CONFIGURED" -ForegroundColor Yellow }
else {
$isOurs = ([string]$sl['command']) -like "*$MARKER*"
Write-Host ("statusLine: CONFIGURED" + $(if ($isOurs) { " (ours)" } else { " (SOMEONE ELSE'S -- install would replace it)" })) -ForegroundColor $(if ($isOurs) { "Green" } else { "Yellow" })
Write-Host " command : $($sl['command'])"
Write-Host " refreshInterval: $($sl['refreshInterval'])"
}
Write-Host " script exists : $(Test-Path -LiteralPath $script) ($script)"
$latest = Join-Path $env:USERPROFILE ".claude\mefor-usage\latest.json"
# A RECEIPT, NOT A CONFIG READ. Whether the settings file names the script says nothing about
# whether it has ever run -- that distinction is the one this repo keeps paying for.
Write-Host " has published : $(Test-Path -LiteralPath $latest) ($latest)" -ForegroundColor $(if (Test-Path -LiteralPath $latest) { "Green" } else { "Yellow" })
Write-Host ""
exit 0
}

$settings = Get-Settings

if ($Uninstall) {
if ($settings['statusLine'] -and ([string]$settings['statusLine']['command']) -like "*$MARKER*") {
$settings.Remove('statusLine')
if ($PSCmdlet.ShouldProcess($SettingsPath, "remove the mefor-usage statusLine")) {
Copy-Item -LiteralPath $SettingsPath -Destination "$SettingsPath.bak-usage" -Force -ErrorAction SilentlyContinue
($settings | ConvertTo-Json -Depth 20) | Set-Content -LiteralPath $SettingsPath -Encoding UTF8
Write-Host "statusLine REMOVED from $SettingsPath" -ForegroundColor Yellow
}
}
else { Write-Host "Nothing to remove: the statusLine is absent or is not ours." -ForegroundColor Yellow }
exit 0
}

if (-not (Test-Path -LiteralPath $script)) {
throw "Collector not found at $script. The primary checkout ($primary) does not carry it yet -- merge the branch that adds it, or advance the primary, before installing."
}

if ($settings['statusLine'] -and ([string]$settings['statusLine']['command']) -notlike "*$MARKER*") {
Write-Host ""
Write-Host "REFUSING: a statusLine is already configured and it is not ours." -ForegroundColor Red
Write-Host " command: $($settings['statusLine']['command'])"
Write-Host ""
Write-Host "Silently replacing someone's status bar is not this script's call. Remove it yourself, or"
Write-Host "merge the two commands by hand, then re-run."
exit 1
}

# The guard is inline so a missing script degrades to a marker rather than erroring into the status bar
# on every single message -- a statusLine that shouts an exception is worse than one that says nothing.
$cmd = "# $MARKER`n" +
"`$s = '$($script -replace "'", "''")'; if (Test-Path -LiteralPath `$s) { & pwsh -NoProfile -File `$s } else { Write-Output '${MARKER}: collector missing' }"

$settings['statusLine'] = [ordered]@{
type = "command"
command = $cmd
refreshInterval = $RefreshInterval
}

if ($PSCmdlet.ShouldProcess($SettingsPath, "install the mefor-usage statusLine")) {
if (Test-Path -LiteralPath $SettingsPath) { Copy-Item -LiteralPath $SettingsPath -Destination "$SettingsPath.bak-usage" -Force }
$json = $settings | ConvertTo-Json -Depth 20
# Never leave the file unparseable: a broken settings.json degrades every session on this machine.
try { $null = $json | ConvertFrom-Json } catch { throw "Refusing to write: generated settings JSON is invalid. $_" }
$json | Set-Content -LiteralPath $SettingsPath -Encoding UTF8
Write-Host ""
Write-Host "statusLine INSTALLED (user level -- every session on this machine)" -ForegroundColor Green
Write-Host " collector : $script"
Write-Host " refreshInterval: $RefreshInterval ms"
Write-Host " publishes to : $(Join-Path $env:USERPROFILE '.claude\mefor-usage\latest.json')"
Write-Host " backup : $SettingsPath.bak-usage"
Write-Host ""
Write-Host " Takes effect in NEWLY STARTED sessions. Interactive only -- never under 'claude -p'."
Write-Host " Then read it with: pwsh -NoProfile -File scripts\coord\usage.ps1"
Write-Host ""
}
Loading
Loading