feat: every agent run is logged with what it spent, and the daemon waits out the Claude session limit - #97
Merged
Merged
Conversation
…its out the Claude session limit (#93) The inbox keeps an `inbox_runs` row per agent run: the pull request and head, the pass (`prepare` or `answer`), the models it used, its duration, turns, cost and tokens, and how it ended. `diffity inbox runs [--json] [--since <days>]` prints that log with totals; a prepared card carries its own share ("· 8 min · $1.20", each run on hover) and the page's footer keeps the day's and the week's. A run that ends on the reviewer's Claude session limit no longer costs the pull request an attempt: the row goes back to queued as "waiting: Claude session limit until 14:00", and no further preparation starts until the reset time the message named — or half an hour ahead when it named none. Polling and reconciling carry on meanwhile, and the pause is kept with the inbox so a restart does not spend a run rediscovering the limit. Part of #93. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bwp5QefjsjMFeq6CK6cT6w
…r, and a reset can be a duration Three findings from the review of #97: - The reconcile clears a queued row's reason every tick, so a paused inbox showed a bare "queued" and only the footer said why. The tick now writes "waiting: preparing paused until 14:00" onto every row it holds back, and takes the moment itself from the deps rather than a boolean. - An answer killed through the abort signal — the daemon stopping, the reader leaving — closes its child as cleanly as one that answered, and was logged as `answered`. It is a `failed` run with "stopped before it answered". - `resets` now takes an optional "at", and a duration ("resets in 3 hours", "resets in 1 hour 30 minutes") counted from the moment the run ended. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bwp5QefjsjMFeq6CK6cT6w
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.
Item (2) of #93: the reviewer can see what the inbox costs, and a session limit no longer eats a pull request's retries.
What
inbox_runsin the inbox store: pull request, head, phase (prepare|validate|answer), model, start and end, duration, turns, cost, tokens, outcome (prepared|skipped|validated|answered|failed|timeout|rate-limited), note.recordRun,runs({ since, prId }),runTotals(since),latestRunsFor(prId, headSha).preparePrnow reportsrun: { startedAt, endedAt, stats }on every outcome and tags a failure with what it failed at (worktree|timeout|budget|rate-limit|agent|bundle); the tick logs one run per prepare, and skips the one where no agent ran. Live answers are logged asanswerruns through arecordRunhanded torealAttendantDeps.rateLimitOf(text, now)reads "You've hit your session limit · resets 2pm (Europe/Stockholm)" into the next instant that clock shows, in the zone named or the reviewer's own; unparseable means no time. A rate-limited prepare re-queues the row with its reason, counts no attempt, and pauses preparing until then (or 30 min). Polling and reconciling carry on.diffity inbox runs [--json] [--since <days>]; the cost on a prepared card (· 8 min · $1.20, each run on hover) and totals plus the pause in the page footer,/api/inboxandinbox status --json.Why
The plan measured the inbox by hand out of session logs, and three pull requests sat
failedon "You've hit your session limit". Both are now the daemon's own bookkeeping.How verified
npm testgreen (cli 62 files / 498 tests, ui 433, +31 tests here),npm run typecheckclean, andinbox runs/inbox statuseyeballed against a seeded store.One deviation from the plan
The plan has
runDaemonkeeppausedUntilin memory; it is kept in the store instead (inbox_state), so a restart does not spend a run rediscovering the limit andinbox statuscan print it without the daemon. The daemon still exposes it throughDaemonStatus.Items 3–5 of #93 (CI fields, the validate pass, the compare script) are untouched.
Part of #93
🤖 Generated with Claude Code
https://claude.ai/code/session_01Bwp5QefjsjMFeq6CK6cT6w