No error is a dead end: crash, click, and your agent is already looking

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Gabriel Brown
2026-08-25 12:50:09 -04:00
parent ada0faf1d1
commit cc7d91d09c
43 changed files with 4648 additions and 327 deletions
+465 -104
View File
@@ -1,47 +1,149 @@
#!/usr/bin/env bash
# How much of the Claude subscription is gone, in the bar.
# How much of each agent subscription is gone, in the bar.
#
# This is the only thing in Panama that reads an authentication token, so most
# of what is pinned here is about that rather than about the number:
# The Claude collector is the only thing in Panama that reads an authentication
# token, so most of what is pinned here is about that rather than about the
# number:
#
# 1. The token never reaches argv. A header passed as an argument is
# 1. The token never reaches argv. A credential passed as an argument is
# world-readable in /proc/<pid>/cmdline for as long as the request takes,
# which is the same rule the password and MOK paths already follow.
# 2. The token never reaches the output. The widget has no business seeing a
# credential and neither does anyone reading the state file.
# which is the same rule the password and MOK paths already follow. The
# collector makes its request in-process, so there is no child to leak it
# through -- and this proves no child is spawned.
# 2. The token never reaches the output. Neither the panel nor anyone reading
# the state directory has any business seeing a credential, and neither
# does the collector's own cache.
# 3. It NEVER refreshes the token and never writes to the credentials file.
# That token expires hourly and Claude Code refreshes it on demand; two
# processes rotating one credential means being silently signed out of
# Claude Code by a status widget, and no bar indicator is worth that.
# 4. An expired token is reported as waiting, not as an error, and no request
# is made with it.
# 4. An expired token is reported honestly and no request is made with it.
# 5. The widget hides unless it was asked for AND there are real numbers. An
# indicator reading "unknown" is worse than an empty space.
#
# And, since the collectors became a fan-out over a directory of records:
#
# 6. A collector the user switched off does not run, and its stale record
# does not linger.
# 7. A record is replaced only by a whole, parseable one.
# 8. The replacement is atomic -- mktemp and mv, never a redirect into the
# file a reader is watching.
#
# Hermetic throughout. The usage endpoint is a local HTTP fixture, the codex
# app-server is a stub, and the fan-out runs stub collectors: nothing here
# reaches Anthropic or starts a real agent.
set -uo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
collector="$repo_dir/config/dot/quickshell/scripts/panama-agent-usage"
scripts="$repo_dir/config/dot/quickshell/scripts"
claude="$scripts/panama-agent-usage-claude"
codex="$scripts/panama-agent-usage-codex"
updater="$scripts/panama-agent-usage-update"
service="$repo_dir/config/dot/quickshell/services/AgentUsage.qml"
widget="$repo_dir/config/dot/quickshell/modules/bar/AgentUsageWidget.qml"
panel="$repo_dir/config/dot/quickshell/modules/bar/AgentUsagePanel.qml"
schema="$repo_dir/config/dot/quickshell/config/PreferenceSchema.qml"
aliases="$repo_dir/config/dot/quickshell/config/Settings.qml"
findings=()
note() { findings+=("$1"); }
[[ -x "$collector" ]] || { printf 'agent usage contract: %s is not executable\n' "$collector" >&2; exit 1; }
for required in "$claude" "$codex" "$updater"; do
[[ -x "$required" ]] || {
printf 'agent usage contract: %s is not executable\n' "$required" >&2
exit 1
}
done
[[ -f "$panel" ]] || { printf 'agent usage contract: missing %s\n' "$panel" >&2; exit 1; }
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
server_pid=""
cleanup() {
[[ -n "$server_pid" ]] && kill "$server_pid" 2>/dev/null
rm -rf "$work"
}
trap cleanup EXIT
stub="$work/bin"
mkdir -p "$stub"
export XDG_STATE_HOME="$work/state"
output="$XDG_STATE_HOME/panama/agent-usage.json"
mkdir -p "$stub" "$work/claude" "$work/cache" "$work/collectors" "$work/usage"
readonly SECRET='sk-fixture-token-must-never-appear'
# ── The endpoint, served locally ────────────────────────────────────────────
#
# A real request to a real socket, so what the collector puts in the header is
# observable -- and so nothing in this file can accidentally reach Anthropic.
cat >"$work/server.py" <<'PY'
import http.server
import os
import sys
work = sys.argv[1]
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
with open(os.path.join(work, "headers.txt"), "a") as handle:
handle.write((self.headers.get("Authorization") or "") + "\n")
try:
status = int(open(os.path.join(work, "status")).read().strip())
except Exception:
status = 200
body = open(os.path.join(work, "payload.json"), "rb").read()
self.send_response(status)
self.send_header("Content-Type", "application/json")
if status == 429:
self.send_header("Retry-After", "30")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *args):
pass
server = http.server.HTTPServer(("127.0.0.1", 0), Handler)
with open(os.path.join(work, "port"), "w") as handle:
handle.write(str(server.server_address[1]))
server.serve_forever()
PY
printf '200\n' >"$work/status"
: >"$work/headers.txt"
cat >"$work/payload.json" <<'JSON'
{
"five_hour": {"utilization": 42, "resets_at": "2099-08-22T14:00:00Z"},
"seven_day": {"utilization": 71, "resets_at": "2099-08-27T00:00:00Z"},
"rate_limit_tier": "default_claude_max_5x",
"limits": [
{"kind": "weekly_scoped", "percent": 31, "resets_at": "2099-08-27T00:00:00Z",
"scope": {"model": {"display_name": "Fable 5", "id": "claude-fable-5"}}}
]
}
JSON
python3 "$work/server.py" "$work" &
server_pid=$!
for _ in $(seq 1 50); do
[[ -s "$work/port" ]] && break
sleep 0.1
done
[[ -s "$work/port" ]] || { printf 'agent usage contract: the endpoint fixture did not start\n' >&2; exit 1; }
endpoint="http://127.0.0.1:$(cat "$work/port")/usage"
# A recording curl. Nothing should ever invoke it: the collector makes its
# request in-process. If it is called, the argv it was called with is evidence.
cat >"$stub/curl" <<STUB
#!/usr/bin/env bash
printf '%s\n' "\$*" >>"$work/curl-argv"
exit 1
STUB
chmod +x "$stub/curl"
: >"$work/curl-argv"
credentials() {
local expires="$1"
cat >"$work/credentials.json" <<CREDS
@@ -50,128 +152,387 @@ credentials() {
CREDS
}
# Records how it was invoked, including whether the secret was in argv.
cat >"$stub/curl" <<STUB
#!/usr/bin/env bash
printf '%s\n' "\$*" >>"$work/curl-argv"
printf '%s\n' '{"five_hour":{"utilization":42,"resets_at":"2026-08-22T14:00:00Z"},"seven_day":{"utilization":71,"resets_at":"2026-08-27T00:00:00Z"},"rate_limit_tier":"default_claude_max_5x"}'
STUB
chmod +x "$stub/curl"
run() {
PATH="$stub:$PATH" PANAMA_AGENT_CREDENTIALS="$work/credentials.json" \
PANAMA_AGENT_USAGE_ENDPOINT="https://example.invalid/usage" \
"$collector" >/dev/null 2>&1
# The collector prints its record; the fan-out is what writes files. Both are
# exercised, so both are seamed.
run_claude() {
PATH="$stub:$PATH" \
CLAUDE_CONFIG_DIR="$work/claude" \
PANAMA_AGENT_CREDENTIALS="$work/credentials.json" \
PANAMA_AGENT_USAGE_ENDPOINT="${FIXTURE_ENDPOINT:-$endpoint}" \
PANAMA_AGENT_USAGE_CACHE="$work/cache" \
"$claude" "$@" 2>"$work/claude-stderr"
}
# ── 4. An expired token waits rather than failing ───────────────────────────
future_ms=$(( ($(date +%s) + 3600) * 1000 ))
# ── 4. An expired token is honest, and makes no request ─────────────────────
: >"$work/curl-argv"
credentials 1000
run
[[ "$(jq -r .status "$output")" == "stale" ]] \
|| note "an expired token did not report as stale (got: $(jq -r .status "$output"))"
[[ -s "$work/curl-argv" ]] \
record="$(run_claude --force)"
[[ "$(jq -r .usageStatusText <<<"$record")" == "Sign-in expired" ]] \
|| note "an expired token did not report as expired (got: $(jq -r .usageStatusText <<<"$record"))"
[[ -s "$work/headers.txt" ]] \
&& note 'a request was made with an expired token'
# ── 1 & 2. The token stays out of argv and out of the output ────────────────
# A credential file that is not there at all is a different sentence.
mv "$work/credentials.json" "$work/credentials.away"
record="$(run_claude --force)"
[[ "$(jq -r .usageStatusText <<<"$record")" == "Waiting for auth" ]] \
|| note "a missing sign-in did not report as waiting (got: $(jq -r .usageStatusText <<<"$record"))"
[[ -s "$work/headers.txt" ]] \
&& note 'a request was made with no token at all'
mv "$work/credentials.away" "$work/credentials.json"
: >"$work/curl-argv"
credentials "$(( ($(date +%s) + 3600) * 1000 ))"
run
[[ "$(jq -r .status "$output")" == "ok" ]] \
|| note "a valid token did not produce a reading (got: $(jq -r .detail "$output"))"
grep -qF "$SECRET" "$work/curl-argv" \
&& note 'the token was passed to curl as an argument, where /proc makes it world-readable'
grep -q -- '--config' "$work/curl-argv" \
|| note 'the token is not passed through a curl config file, so it may reach argv'
grep -qrF "$SECRET" "$XDG_STATE_HOME" \
&& note 'the token appears in the state file the widget reads'
# ── 3. Credentials are never written ────────────────────────────────────────
# ── 1, 2 & 3. What happens to the token ─────────────────────────────────────
credentials "$future_ms"
before="$(sha256sum "$work/credentials.json" | cut -d' ' -f1)"
run
record="$(run_claude --force)"
[[ "$(jq -r .ready <<<"$record")" == "true" ]] \
|| note "a valid token did not produce a reading (status: $(jq -r .usageStatusText <<<"$record"))"
grep -qF "Bearer $SECRET" "$work/headers.txt" \
|| note 'the token did not reach the Authorization header, so the request was not authenticated'
[[ -s "$work/curl-argv" ]] \
&& note 'the collector shelled out to curl, where /proc makes an argument world-readable'
grep -qF "$SECRET" <<<"$record" \
&& note 'the token appears in the record the panel reads'
grep -qrF "$SECRET" "$work/cache" \
&& note "the token appears in the collector's own cache"
grep -qF "$SECRET" "$work/claude-stderr" \
&& note 'the token appears on stderr, where the journal keeps it'
[[ "$(sha256sum "$work/credentials.json" | cut -d' ' -f1)" == "$before" ]] \
|| note 'the collector modified the credentials file'
# Comments stripped: the header explains at length that it does not refresh,
# and matching that is matching documentation.
uncommented() { grep -v '^[[:space:]]*#' "$1"; }
uncommented "$collector" | grep -qE 'refreshToken|refresh_token|grant_type' \
uncommented() { python3 - "$1" <<'PY'
import re, sys
text = open(sys.argv[1], encoding="utf-8").read()
# Module docstring first, then whole-line comments.
text = re.sub(r'^\s*(?:"""|\'\'\')(?:.|\n)*?(?:"""|\'\'\')', "", text, count=1)
print("\n".join(line for line in text.split("\n") if not line.lstrip().startswith("#")))
PY
}
uncommented "$claude" | grep -qE 'refreshToken|refresh_token|grant_type' \
&& note 'the collector touches the refresh token, which can sign Claude Code out'
uncommented "$collector" | grep -qE '>[[:space:]]*"?\$?\{?CREDENTIALS' \
uncommented "$claude" | grep -qE 'credentials.*\.write_text|open\([^)]*credentials[^)]*"w"' \
&& note 'the collector writes to the credentials file'
# ── The reading it produced ─────────────────────────────────────────────────
# The endpoint already reports percentages. Treating them as 0..1 fractions and
# multiplying is how the bar came to read 1500%.
[[ "$(jq -r '.usage.fiveHour.used' "$output")" == "42" ]] \
|| note "the five-hour reading is $(jq -r '.usage.fiveHour.used' "$output") for a reported 42%"
[[ "$(jq -r '.usage.week.used' "$output")" == "71" ]] \
|| note "the weekly reading is $(jq -r '.usage.week.used' "$output") for a reported 71%"
# Nothing the widget shows may fall outside the range a percentage has, whatever
# the endpoint says. A readout that can print 1500% is one you learn to ignore.
cat >"$stub/curl" <<'STUB'
#!/usr/bin/env bash
printf '%s\n' '{"five_hour":{"utilization":1500},"seven_day":{"utilization":-4}}'
STUB
chmod +x "$stub/curl"
run
[[ "$(jq -r '.usage.fiveHour.used' "$output")" == "100" ]] \
|| note 'an out-of-range reading was not clamped to 100'
[[ "$(jq -r '.usage.week.used' "$output")" == "0" ]] \
|| note 'a negative reading was not clamped to 0'
cat >"$stub/curl" <<'STUB'
#!/usr/bin/env bash
printf '%s\n' '{"five_hour":{"utilization":42,"resets_at":"2026-08-22T14:00:00Z"},"seven_day":{"utilization":71,"resets_at":"2026-08-27T00:00:00Z"},"rate_limit_tier":"default_claude_max_5x"}'
STUB
chmod +x "$stub/curl"
# ── It answers a click ──────────────────────────────────────────────────────
#
# Pill's MouseArea is gated on `interactive`, so a widget that sets it false and
# connects onSecondaryActivated has a handler nothing can ever reach.
uncommented "$widget" | grep -q 'interactive: false' \
&& note 'the widget disables Pill''s mouse area, so its click handlers never fire'
uncommented "$widget" | grep -q 'onActivated' \
|| note 'left-clicking the widget does nothing'
# The endpoint reports percentages already. Treating them as 0..1 fractions and
# multiplying is how the bar once came to read 1500%.
# An endpoint that answers with something else must degrade, not crash.
cat >"$stub/curl" <<'STUB'
#!/usr/bin/env bash
printf '%s\n' '{"error":{"message":"nope"}}'
# Whole percents: jq prints 1 and 1.0 for the same number, and a contract that
# fails on the spelling of a float teaches nothing.
percent_for() { jq -r --arg label "$1" '.limits[] | select(.label == $label) | .percent * 100 | round' <<<"$record"; }
[[ "$(percent_for 'Session (5-hour)')" == "42" ]] \
|| note "the five-hour reading is $(percent_for 'Session (5-hour)')% for a reported 42%"
[[ "$(percent_for 'Weekly (7-day)')" == "71" ]] \
|| note "the weekly reading is $(percent_for 'Weekly (7-day)')% for a reported 71%"
# The model-scoped window lives only in the `limits` array. A collector reading
# the flat buckets alone drops a limit the account is really spending against.
[[ "$(percent_for 'Fable 5 Weekly')" == "31" ]] \
|| note 'the model-scoped limit from the limits array was not read'
[[ "$(jq -r .tierLabel <<<"$record")" == "Max 5x" ]] \
|| note "the plan label is $(jq -r .tierLabel <<<"$record"), not the Max 5x the credential names"
# Nothing the panel shows may fall outside the range a percentage has, whatever
# the endpoint says.
cat >"$work/payload.json" <<'JSON'
{"five_hour": {"utilization": 1500}, "seven_day": {"utilization": -4}}
JSON
rm -f "$work/cache/claude-limits.json"
record="$(run_claude --force)"
[[ "$(percent_for 'Session (5-hour)')" == "100" ]] \
|| note "an out-of-range reading was not clamped (got: $(percent_for 'Session (5-hour)'))"
[[ -z "$(percent_for 'Weekly (7-day)')" ]] \
|| note 'a negative reading was reported as a percentage rather than dropped'
# A payload that speaks the older fraction convention is read as fractions.
cat >"$work/payload.json" <<'JSON'
{"five_hour": {"utilization": 0.42}, "seven_day": {"utilization": 0.71}}
JSON
rm -f "$work/cache/claude-limits.json"
record="$(run_claude --force)"
[[ "$(percent_for 'Session (5-hour)')" == "42" ]] \
|| note "a fraction-scaled payload was misread as $(percent_for 'Session (5-hour)')%"
# ── A cached limit outlives its probe, but not its window ───────────────────
#
# Once a window has reset, a cached figure describes a period that is over. A
# stale 78% on a fresh week is a lie the panel must not tell.
dead_endpoint="http://127.0.0.1:1/usage"
cat >"$work/cache/claude-limits.json" <<'JSON'
{"fetchedAtMs":0,"limits":[{"label":"Weekly (7-day)","percent":0.78,"resetsAt":"2099-01-01T00:00:00+00:00"}]}
JSON
record="$(FIXTURE_ENDPOINT="$dead_endpoint" run_claude --force)"
[[ "$(percent_for 'Weekly (7-day)')" == "78" ]] \
|| note 'a cached limit whose window is still open was thrown away'
cat >"$work/cache/claude-limits.json" <<'JSON'
{"fetchedAtMs":0,"limits":[{"label":"Weekly (7-day)","percent":0.78,"resetsAt":"2000-01-01T00:00:00+00:00"}]}
JSON
record="$(FIXTURE_ENDPOINT="$dead_endpoint" run_claude --force)"
[[ -z "$(percent_for 'Weekly (7-day)')" ]] \
&& [[ "$(jq -r .usageStatusText <<<"$record")" == "Claude limits unavailable" ]] \
|| note 'a cached limit was reported after its window had already reset'
# ── A transport failure is not an answer ────────────────────────────────────
#
# Nothing reached a server: retry sooner than the interval. An HTTP status IS a
# server, and pestering it is how a rate limit becomes a ban.
rm -f "$work/cache/claude-limits.json"
record="$(FIXTURE_ENDPOINT="$dead_endpoint" run_claude --force)"
[[ "$(jq -r '.retryAdvised // false' <<<"$record")" == "true" ]] \
|| note 'a transport failure did not ask the shell to retry sooner'
printf '500\n' >"$work/status"
record="$(run_claude --force)"
[[ "$(jq -r '.retryAdvised // false' <<<"$record")" == "false" ]] \
|| note 'an HTTP error asked for a fast retry, which turns a bad status into a hammer'
printf '429\n' >"$work/status"
record="$(run_claude --force)"
grep -q 'retry after 30s' <<<"$(jq -r .authHelpText <<<"$record")" \
|| note "a 429 did not carry its retry-after into the help text (got: $(jq -r .authHelpText <<<"$record"))"
printf '200\n' >"$work/status"
# ── The Codex collector asks read-only, and reads the right field ───────────
cat >"$stub/codex" <<'STUB'
#!/usr/bin/env python3
import json
import os
import sys
with open(os.environ["CODEX_STUB_ARGV"], "w") as handle:
handle.write(" ".join(sys.argv[1:]))
RESULTS = {
"initialize": {"userAgent": "stub"},
"account/read": {"account": {"type": "chatgpt", "planType": "prolite"}},
"account/rateLimits/read": {
"rateLimits": {
"planType": "prolite",
"primary": {"usedPercent": 55, "windowDurationMins": 10080, "resetsAt": 4102444800},
"secondary": None,
},
"rateLimitsByLimitId": {
"codex": {"limitName": None, "primary": {"usedPercent": 55, "windowDurationMins": 10080}},
"codex_spark": {
"limitName": "Spark",
"primary": {"usedPercent": 12, "windowDurationMins": 300, "resetsAt": 4102444800},
"secondary": None,
},
},
},
}
for line in sys.stdin:
line = line.strip()
if not line:
continue
try:
message = json.loads(line)
except Exception:
continue
if "id" not in message:
continue
print(json.dumps({"id": message["id"], "result": RESULTS.get(message.get("method"), {})}), flush=True)
STUB
chmod +x "$stub/curl"
run
[[ "$(jq -r .status "$output")" == "unavailable" ]] \
|| note 'an error response was not reported as unavailable'
chmod +x "$stub/codex"
# ── 5. The widget hides itself ──────────────────────────────────────────────
mkdir -p "$work/codex/sessions/2026/08/25"
cat >"$work/codex/sessions/2026/08/25/session.jsonl" <<'JSONL'
{"type":"turn_context","payload":{"model":"gpt-5.6-sol"}}
{"type":"token_count","timestamp":"2026-08-25T10:00:00Z","payload":{"type":"token_count","info":{"total_token_usage":{"input_tokens":999999,"output_tokens":999999},"last_token_usage":{"input_tokens":1200,"cached_input_tokens":1000,"output_tokens":300}}}}
{"type":"token_count","timestamp":"2026-08-25T10:05:00Z","payload":{"type":"token_count","info":{"total_token_usage":{"input_tokens":1999999,"output_tokens":1999999},"last_token_usage":{"input_tokens":1200,"cached_input_tokens":1000,"output_tokens":300}}}}
JSONL
touch "$work/codex/sessions/2026/08/25/session.jsonl"
codex_record="$(
PATH="$stub:$PATH" \
CODEX_HOME="$work/codex" \
CODEX_STUB_ARGV="$work/codex-argv" \
PANAMA_AGENT_CODEX_BIN="$stub/codex" \
PANAMA_AGENT_USAGE_CACHE="$work/cache" \
"$codex" --force 2>/dev/null
)"
grep -q -- '-s read-only' "$work/codex-argv" \
|| note "the codex app-server was not asked for read-only ($(cat "$work/codex-argv" 2>/dev/null))"
grep -q -- 'app-server' "$work/codex-argv" \
|| note 'the codex collector did not start the app-server'
[[ "$(jq -r '.limits[] | select(.label == "Weekly (7-day)") | .percent * 100 | round' <<<"$codex_record")" == "55" ]] \
|| note 'the codex weekly limit was not read from the app-server'
[[ "$(jq -r '.limits[] | select(.label == "Spark Session (5-hour)") | .percent * 100 | round' <<<"$codex_record")" == "12" ]] \
|| note 'the codex model-scoped limit was not read'
[[ "$(jq -r .tierLabel <<<"$codex_record")" == "prolite" ]] \
|| note 'the codex plan was not read'
# total_token_usage is cumulative for the session. Counting it instead of
# last_token_usage makes usage grow quadratically: these two snapshots are
# 1500 tokens each, not four million.
[[ "$(jq -r .todayTotalTokens <<<"$codex_record")" == "3000" ]] \
|| note "the codex scan counted $(jq -r .todayTotalTokens <<<"$codex_record") tokens where the last-turn rule gives 3000"
# ── 6, 7 & 8. The fan-out ───────────────────────────────────────────────────
cat >"$work/collectors/panama-agent-usage-claude" <<STUB
#!/usr/bin/env bash
printf 'claude\n' >>"$work/ran"
printf '%s\n' '{"schemaVersion":1,"id":"claude","name":"Claude Code","ready":true,"limits":[]}'
STUB
cat >"$work/collectors/panama-agent-usage-codex" <<STUB
#!/usr/bin/env bash
printf 'codex\n' >>"$work/ran"
printf '%s\n' '{"schemaVersion":1,"id":"codex","name":"Codex","ready":true,"limits":[]}'
STUB
chmod +x "$work/collectors"/panama-agent-usage-*
run_update() {
PANAMA_AGENT_USAGE_COLLECTORS="$work/collectors" \
PANAMA_AGENT_USAGE_DIR="$work/usage" \
PANAMA_SETTINGS="$work/settings.json" \
"$updater" "$@" 2>"$work/update-stderr"
}
# Both on: both records appear.
printf '%s\n' '{"agentUsageClaude":true,"agentUsageCodex":true}' >"$work/settings.json"
: >"$work/ran"
run_update
[[ -s "$work/usage/claude.json" && -s "$work/usage/codex.json" ]] \
|| note 'the fan-out did not write a record for every enabled collector'
# One off: it must not run at all. "Off" has to mean "no process" -- the
# collector is the thing that reads a credential and makes a request.
printf '%s\n' '{"agentUsageClaude":true,"agentUsageCodex":false}' >"$work/settings.json"
: >"$work/ran"
run_update
grep -qx 'codex' "$work/ran" \
&& note 'a collector the user switched off still ran'
[[ -e "$work/usage/codex.json" ]] \
&& note "a disabled collector's stale record was left behind for the panel to show"
# Absent means default, and the default is on. `.key // true` would read false
# as absent, which is how a switched-off collector comes back to life.
printf '%s\n' '{}' >"$work/settings.json"
: >"$work/ran"
run_update
grep -qx 'codex' "$work/ran" \
|| note 'an unset per-agent preference was not treated as its default of on'
# 7. A record is replaced only by a whole, parseable one.
printf '%s\n' '{"agentUsageClaude":true,"agentUsageCodex":true}' >"$work/settings.json"
run_update
good="$(sha256sum "$work/usage/codex.json" | cut -d' ' -f1)"
cat >"$work/collectors/panama-agent-usage-codex" <<'STUB'
#!/usr/bin/env bash
printf '%s\n' 'Traceback (most recent call last):'
exit 1
STUB
chmod +x "$work/collectors/panama-agent-usage-codex"
run_update
[[ "$(sha256sum "$work/usage/codex.json" | cut -d' ' -f1)" == "$good" ]] \
|| note 'a collector that died mid-run replaced a good record with rubbish'
jq -e . >/dev/null 2>&1 <"$work/usage/codex.json" \
|| note 'the usage directory holds something that is not JSON'
# 8. Atomic, and tidy: no temp file survives a failure for the panel to read.
compgen -G "$work/usage/.*.??????" >/dev/null \
&& note 'the fan-out left a temporary record behind in the directory the panel watches'
grep -qE '>[[:space:]]*"\$USAGE_DIR/\$agent\.json"' "$updater" \
&& note 'the fan-out redirects straight into the file a reader is watching'
grep -q 'mktemp' "$updater" && grep -q 'mv "\$tmp"' "$updater" \
|| note 'the fan-out does not write through mktemp and mv, so a reader can catch a half-written record'
# ── 5. The widget hides itself, and the panel is what it opens ──────────────
qml_uncommented() { grep -v '^[[:space:]]*//' "$1"; }
grep -q 'Settings.showAgentUsage && AgentUsage.available' "$widget" \
|| note 'the widget does not gate on both the preference and having real numbers'
qml_uncommented "$widget" | grep -q 'interactive: false' \
&& note "the widget disables Pill's mouse area, so its click handlers never fire"
qml_uncommented "$widget" | grep -q 'onActivated' \
|| note 'left-clicking the widget does nothing'
qml_uncommented "$widget" | grep -q 'onSecondaryActivated: ShellState.openSettings' \
|| note 'right-clicking the widget no longer opens the settings that govern it'
qml_uncommented "$widget" | grep -q 'AgentUsagePanel' \
|| note 'the widget does not open the usage panel'
grep -q 'key: "showAgentUsage"' "$schema" || note 'there is no showAgentUsage preference'
grep -A2 'key: "showAgentUsage"' "$schema" | grep -q 'def: false' \
|| note 'the usage widget is on by default; it is a coding-tool readout, not general-purpose desktop furniture'
grep -q 'showAgentUsage' "$aliases" \
|| note 'Settings.qml does not alias showAgentUsage, so the binding reads undefined'
for key in showAgentUsage agentUsageClaude agentUsageCodex agentUsageRefreshMinutes; do
grep -q "\"$key\"" "$aliases" \
|| note "Settings.qml does not alias $key, so the binding reads undefined"
done
# The two-tier colouring is better than one threshold and is not to be lost in
# a refactor: amber before it hurts, red when it is about to.
qml_uncommented "$widget" | grep -q 'AgentUsage.headline >= 90' \
|| note 'the widget lost its red tier at 90%'
qml_uncommented "$widget" | grep -q 'AgentUsage.headline >= 75' \
|| note 'the widget lost its amber tier at 75%'
# The collectors run on a timer measured in minutes, never on a repaint, and
# only while the readout was asked for.
python3 - "$service" <<'PY' || note 'the collectors are not run on a minute-scale timer driven by the preference'
import re
import sys
# The collector runs on a timer measured in minutes, never on a repaint.
python3 - "$service" <<'PY' || note 'the collector is not run on a minute-scale timer'
import re, sys
text = open(sys.argv[1], encoding="utf-8").read()
match = re.search(r"interval:\s*(\d+)\s*\*\s*60\s*\*\s*1000", text)
if not match or int(match.group(1)) < 1:
if not re.search(r"interval:\s*root\.refreshMinutes\s*\*\s*60\s*\*\s*1000", text):
raise SystemExit(1)
if "Settings.agentUsageRefreshMinutes" not in text:
raise SystemExit(1)
if not re.search(r"running:\s*Settings\.showAgentUsage", text):
raise SystemExit(1)
PY
# The panel and the service read records; neither has any business knowing what
# a credential looks like.
for file in "$service" "$panel" "$widget"; do
grep -qE 'credentials|accessToken|Authorization' "$file" \
&& note "$(basename "$file") mentions a credential; only the collector may"
done
# Nothing in the panel repaints on a clock. The one timer it owns advances a
# displayed time and only runs while the panel is open.
python3 - "$panel" <<'PY' || note 'the panel animates or ticks while it is closed'
import re
import sys
text = open(sys.argv[1], encoding="utf-8").read()
for block in re.findall(r"Timer\s*\{(.*?)\n \}", text, re.S):
if "running: root.visible" not in block:
raise SystemExit(1)
if re.search(r"\b(SequentialAnimation|loops:\s*Animation\.Infinite)\b", text):
raise SystemExit(1)
PY
# ── The retired collector stays retired ─────────────────────────────────────
[[ -e "$scripts/panama-agent-usage" ]] \
&& note 'the single-agent collector is still on disk; its consumers moved to the fan-out'
grep -rqF 'scripts/panama-agent-usage"' "$repo_dir/config" "$repo_dir/bin" 2>/dev/null \
&& note 'something still calls the retired single-agent collector'
if (( ${#findings[@]} > 0 )); then
printf 'agent usage contract: %d finding(s)\n' "${#findings[@]}" >&2
printf ' - %s\n' "${findings[@]}" >&2