Skip to content

fix(usage): the middle wildcard is a read too — usage_collect + activity_collect (DIVE-3419) - #648

Open
lodar wants to merge 2 commits into
mainfrom
dive-3419-usage-middle-wildcard
Open

fix(usage): the middle wildcard is a read too — usage_collect + activity_collect (DIVE-3419)#648
lodar wants to merge 2 commits into
mainfrom
dive-3419-usage-middle-wildcard

Conversation

@lodar

@lodar lodar commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Both transcript readers in cmd_usage.sh read projects/*/*.jsonl. Found by dev3 while fixing DIVE-3417 (the same shape in _spend_scan_task_ids).

usage_collect — guarded top and bottom, of a THREE-level path

probe_readable covers projects/; the per-file except OSError covers each *.jsonl. Between them sat a third read nothing covered: the middle * is a directory listing that glob.glob() performs itself and swallows every OSError from, yielding nothing for that entry. An unreadable project subdir therefore left the agent in the READABLE set with a total that is a fraction of the truth and coverage.complete still true — so usage.sh's "cannot read" and "⚠ N NOT checked — burn is unknown (not 0)" surfaces stayed quiet on a short number. 5dive usage is what a human reads when deciding whether a seat is burning.

Milder than DIVE-3417 (nothing here persists over durable state) but the same defect. No third probe could fix it — there is no exception to catch. Each level is enumerated with os.listdir, which may raise. The agent now lands in coverage.unreadable with a named cause, exactly where an unreadable FILE already put it.

ENOENT and ENOTDIR skips are kept deliberately — both mean "nothing unread here", and re-reporting them as blindness over-fires on every idle agent, which disables coverage as thoroughly as the fail-open did.

activity_collect — the decision acceptance 4 asked for, recorded at the site

It had no readability contract at any level (bare except OSError: continue throughout) and still resolved home through the guessed /home/agent-<name> fallback DIVE-3345 deleted from the sibling.

Decision: it owes a contract, but a REPORTING one, not the fail-closed NOT-REACHED one _spend_scan_task_ids owes. That function must emit no number when blind because its caller PERSISTS what it returns over durable state and a short total silently becomes the record. Nothing consumes this trail that way — one human, one agent, read next to the trail itself. Refusing to render 40 commands because one project dir is mode 000 destroys the surface to protect a token count 5dive usage reports properly anyway. So: emit the trail, and name what is missing from it (.partial, printed above the counts by cmd_activity). What is refused is the old rendering, where "unreadable" and "did nothing" were the same output. The guessed path is gone — an unresolvable agent exits non-zero, empty stdout, named cause.

Widening

glob hid dot-prefixed names at both levels. A dot-prefixed session file is still burn; not counting it silently is the shape being refused.

Grade — tests/usage_middle_wildcard_unit.sh, uid 1011, unprivileged, same harness both sides

9 passed / 14 failed pre-fix (78b6a6a) · 23 / 0 after.

Headline red, which is dev3's reported shape reproduced exactly:

FAIL - EACCES half total still emitted as complete
   [{"name":"alpha","total":30000,...}]          <- expected 60000
   {"agentsExpected":1,"agentsRead":1,"unreadable":[],"complete":true}

Arms, built to DIVE-3345/3417's rules: anchor first (a healthy non-zero total on the same path — every refusal arm is vacuous while it is red); paired sick/healed (each sick fixture re-run healed must recompute the real 60000 through the same code, proving the arm read blindness and not emptiness); an ANY-UID armENOTDIR does not work at this level (it is a legitimate skip here), so the vehicle is ELOOP, a self-referential symlink where a project dir belongs, which root cannot resolve either, so a uid-0 CI run cannot be a vacuous green; and over-fire controls (never-run agent, stray file in projects/).

Honest caveat on the pre-fix reds: the activity_collect arms are red pre-fix partly because A_HOME_ROOT (the test seam) does not exist there, so those fixtures resolved nowhere. That is itself acceptance 4's defect firing — pre-fix activity_collect guessed /home/agent-alpha, read nothing, and returned a well-formed empty trail with rc 0 — but it conflates two defects. The unambiguous middle-wildcard reproduction is the usage_collect block above, which uses USAGE_HOME_ROOT and exists on both trees.

Siblings unmoved on the fixed tree: usage_coverage 11/0 · usage_dispatch_flag 15/0 · usage_enumeration_completeness 9/0 · usage_presenter_coverage 26/0 · heartbeat_usage_heal 27/0 · loop_spend_not_reached 17/0 · loop_ceiling_enforce 5/0 · task_budget_enforce 41/0.

Not in scope, named so nobody re-derives it

The shell globs in cmd_memory.sh / cmd_doctor.sh share the unguarded middle wildcard but fail LOUD ("no memory stores found") rather than producing a number. A skipped level that cannot be mistaken for a measurement is not this defect.

5dive-cli does not auto-deploy (deliberate release-cut), so merge is not a ship.

🤖 Generated with Claude Code

lodar and others added 2 commits August 15, 2026 22:30
…ity_collect (DIVE-3419)

usage_collect was guarded at the TOP (probe_readable on projects/) and the
BOTTOM (per-file except OSError) of a three-level path. The middle `*` of
`projects/*/*.jsonl` is a directory listing that glob.glob() performs itself and
swallows every OSError from, so an unreadable project subdir left the agent in
the READABLE set with a FRACTION of its total and coverage.complete still true —
and every '⚠ N NOT checked — burn is unknown (not 0)' surface built on that flag
stayed quiet on it. Replaced with per-level os.listdir (which may raise); the
agent now lands in coverage.unreadable with a named cause, where an unreadable
FILE already put it. ENOENT/ENOTDIR skips kept so a rolling session and a stray
file in projects/ do not over-fire.

activity_collect had no readability contract at ANY level and still resolved
home through the guessed /home/agent-<name> fallback DIVE-3345 deleted from the
sibling scanner. Decision recorded at the site: it DOES owe a contract, but a
REPORTING one, not the fail-closed NOT-REACHED its spend-scanning sibling owes —
nothing persists this trail over durable state, it is read by one human next to
the trail itself, and refusing to render 40 commands over one mode-000 dir
destroys the surface. So it emits the trail and NAMES what is short of it
(.partial, printed above the counts). The guessed path is gone: an unresolvable
agent now exits non-zero with an empty stdout and a named cause.

Grade, uid 1011, unprivileged, same harness both sides: 9/14 pre-fix (78b6a6a),
23/0 after. Headline red: total=30000 against an expected 60000, shipped with
coverage.complete=true. Siblings unmoved on the fixed tree: usage_coverage 11/0,
usage_dispatch_flag 15/0, usage_enumeration_completeness 9/0,
usage_presenter_coverage 26/0, heartbeat_usage_heal 27/0, loop_spend_not_reached
17/0, loop_ceiling_enforce 5/0, task_budget_enforce 41/0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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