Repository navigation
feat(records)!: split each session's time into categories with a cheap model - #725
Conversation
Quest-Claim: 9842f2c8a03d217a
… model The record sender labels each new transcript step with a fixed category through Haiku (else Sol low), sums each category's time, less timed waits, and tokens from the transcript itself, and sends the split with a line on what went wrong. A sampled tenth is labeled again by a stronger model, and the split reports the time the two disagree on. `quest usage` prints the splits per PR and per role from the kept labels. Codex review turns now run with connected apps off, and a question about data alone runs with no tools on either CLI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, clip to the window, and skip injected Codex messages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 1 minute. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
There was a problem hiding this comment.
ℹ️ No critical issues — three rough edges in how labeling failures are handled, inline.
Reviewed changes
I reviewed the full PR at 2c8b5dd: the new time split and the record, sender, usage, and review changes that support it.
SessionRecord.split: a requiredTimeSplit | nullwith fixedSPLIT_CATEGORIES.parseSplitchecks every field, and a split is refused whenwaitsis null.src/cli/split.ts: reads Claude Code and Codex transcripts into actions, marks, and turns. A model labels each action, and labels are kept per session behind a digest so each stop labels only new actions.splitOfcomputes time and tokens per category from the transcript, after subtracting merged waits and clipping tosince.- Sender: labels each local session it emits before building the records. A labeling failure goes through the group's existing backoff.
ask()refactor: the classifier's fallback loop is now shared. A question withtools: ""runs Claude with--tools ""and Codex with its tools off and--skip-git-repo-check.- Codex apps off: every Codex run now passes
features.apps=false, which closes the exposure of connected apps described in the PR body. quest usage: adds a per-PRtime:line,went wrongnotes, aTime by rolerollup, and asplitsarray in--json. All of it is read from kept labels.
ℹ️ The Claude labeling path has never been run against a real model
The PR body says every real run fell through to Sol, because this host's Claude CLI is not logged in. So claude -p --tools "" --json-schema … with Haiku and Sonnet has never answered a labeling question. Community reports about toolless --json-schema are mixed. Some say it works, and one says toolless sessions sometimes return placeholder keys. On a host where Haiku is logged in, a bad answer would not fall through to Sol, as the first inline comment explains. It would fail every pass instead. One real Haiku labeling run before merge would settle this.
claude-opus-5-5 | 𝕏
|
quest land watch: ended this pull request's pick, disarming its auto-merge, since its head 2c8b5dd is held by finding by pullfrog[bot]: A labeling answer that arrives but is unusable fails the whole repository group's upload, and the next model is never tr (#725 (comment)); finding by pullfrog[bot]: When the checker fails, the splitter's labels are thrown away even though they were valid and already paid for. If the c (#725 (comment)); finding by pullfrog[bot]: A note that runs past (written by quest land watch) |
An unusable reply now passes the part to the next model, a session no model could label is sent unsplit and said, a check no model could give leaves its part unchecked while keeping the splitter's labels, and a long note is cut rather than failing its part. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found. The three earlier findings are fixed, and the fixes add no new problems.
Reviewed changes
Since the review at 2c8b5dd, one commit changes how labeling failures are handled. It moves reply validation inside the model fallback and makes labeling failures non-blocking.
ask<T>validates inside the fallback:Question.parseruns on each reply. A missing session, a reply that is not JSON, or aparsethrow now becomes aReviewInputError, so the question passes to the next model.Answer.spentkeeps every turn that replied. The risk classifier uses the same path, so a malformed classifier reply now exits throughunanswered.- The splitter parses in
ask:parseLabelsruns as the question'sparse. A note pastNOTE_LIMITis cut byoneLineand no longer fails the part.addUsagesumsspent, so unusable replies are counted whenever some model answered. - A failed check is skipped: if no checker can answer, with
ReviewInputErrororReviewLimitError, the part stays unchecked and the splitter's paid labels are still written. Any other error still propagates. - The sender never holds a record back: any labeling error except
ENOENTbecomes a per-session note. The record goes out withsplit: null, the group settles, and the next stop reports why. Labels are still written after each part, so the parts already labeled are kept. - Tests and README: the tests cover classifier fallthrough on unusable replies, checker failure keeping labels, a cut note that
parseRecordaccepts, and an unsplit send followed by labeling on the next stop. The README and--helptext describe the new behavior.
The Claude labeling path is still untested against real Haiku and Sonnet. The risk is lower now, because an unusable Claude reply falls through to Sol instead of failing the pass. The tokens lost when every checker replies unusably are the open quest review finding listed under Waiting in the PR body, so this review does not raise them again.
claude-opus-5-5 | 𝕏
|
Maintainer decision (2026-11-14, /quest-run): round-3 design checkpoint
(Written by Claude Opus 5.5) |
…me-split # Conflicts: # README.md # src/cli/send.ts # src/cli/usage.ts # src/core/record.ts
ask's failure now carries every turn that replied, as UnansweredError, and labelSession adds them to the split's usage for a skipped check and for a part left unlabeled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found. Tokens spent on unusable replies are now counted even when no model answers, which settles the last open accounting gap.
Reviewed changes
Since the review at f41c370, there is a merge from main and one fix commit. The merge only brings in main's breakage work and its integration in send.ts and usage.ts.
UnansweredErrorcarriesspent: when no model answers usably,asknow throwsUnansweredError, a subclass ofReviewInputError, with every turn that replied. When every failure is a usage limit,askstill throws the earliestReviewLimitError. That loses nothing, because a model at its limit never reachesspent.push.labelSessionrecords failed tries: a check no model could give adds itsspentBy(error)usage, and the part keeps the splitter's labels. When no splitter could label a part, the code writeslabeledwith only the added usage before rethrowing. The digest and labels are unchanged, so the next stop'sreadLabeledkeeps that cost and labels the part again.- Tests: the new
asktest checks that both unusable turns appear on the error. The new split test covers an unusable checker and an unusable splitter, and checks that the splitter's cost is still there on the next run.
claude-opus-5-5 | 𝕏

Finishes quest/m0/benchmark/time-split.md: every session record now says where its working time and tokens went beyond its timed waits, as a cheap model sorted its steps, with a line on what went wrong and a measured classifier error.
Summary
SessionRecord.split(src/core/record.ts): per category{ms, tokens}over a fixed list (understanding,researching,writing,testing,environment,git,forge,reviewing,rework,coordinating,operating,reporting,circles,waiting,other, plus the measuredidle), one-linenotes, acheck(time a stronger model labeled again, and the part it labeled differently), andusage(what labeling took, by model).parseRecordvalidates it whole. The list carries a dated comment, since changing it breaks the trend.src/cli/split.ts: reads a Claude Code or Codex transcript into numbered actions (each call or message, trimmed, with its result trimmed), asks the model to label them as ranges, and computes time and tokens from the transcript itself. Each stretch goes to the action it ends at, a turn's message ends anidlestretch, and timed waits are subtracted. The categories plus the waits therefore sum to the span exactly, and the tokens to the transcript's, whatever the model judges. Labels are kept under the sender's state directory, so each stop labels only the new actions. A digest of the labeled actions detects a transcript that reads differently and labels it afresh.send.ts): labels each emitted local session before building its records. Labeling never holds a record back. A session whose transcript can't be read, or that no model could label, is sent withsplit: nulland the queue settles; the next stop says why, and the session's next stop labels the parts still unlabeled.asktakes aparse, so an unusable reply (not JSON, no session, or labels that don't cover the part) passes the question to the next model. A check no model could give leaves its part unchecked and keeps the splitter's labels. What unusable replies took still counts insplit.usage, sinceaskthrowsUnansweredErrorwith every turn that replied. A note pastNOTE_LIMITis cut, not refused.models.ts):SPLITTERS(Haiku low, else Sol low) andSPLIT_CHECKERS(Sonnet medium, else Sol medium), so the model check retunes them.review.ts: the risk classifier's one-turn loop became a sharedask(), which the splitter reuses. A question withtools: ""runs Claude with--tools ""and Codex with shell, exec, multi-agent, image, browser, computer-use, goals, view-image, and web search off.quest usage: a per-PRtime:line andwent wrongnotes, plus aTime by rolerollup with disagreement and labeling tokens. It reads only kept labels and never asks a model.Security finding fixed on the way
Under
quest review's existing flags, Codex still exposed the user's connected ChatGPT apps (mail, a GitHub app with merge and write tools, Drive, and others) withapproval_policy="never"; the read-only sandbox does not cover them. A diff under review or a transcript being classified could ask for them. Every Codex turnquest reviewruns now passesfeatures.apps=false. Reviewers and the risk classifier keep their shell, which they use to read the diff. Verified by askingcodex execwith these flags to list its tools and to read/etc/hostname: apps and web were gone, and it answeredNOACCESS.Choices made without asking
researching,environment,coordinating,operating,reporting, andwaitingbeyond the Plan's examples, because the sample spent real time there.idleis measured, never judged.quest usageshows the total. Rejected: re-labeling the whole transcript each stop, which is quadratic, and waiting for a session to go quiet, which the queue has no signal for.split.usage, not a separate priced record. The classifier'sclaude -p/codex execsessions run from the state directory, so no PR claims them.questprices nothing itself, and emitting them as their own records would need new lineage plumbing. The report shows what measuring takes, by model.parseRecordrefuses a multi-line or longer note. Nothing can verify mechanically that it quotes nothing.claude-haiku-5-5, as briefed.Public API and wire impact
SessionRecordgains a requiredsplitfield (TimeSplit | null), andparseRecordrefuses a record without it. A store parsing with an olderquest/corerefuses new uploads as having an unknown fieldsplit. To migrate, the store updatesquest/core. Nothing stores records in production yet (records-endpoint still waits on the store).quest/coreexportsSPLIT_CATEGORIES,SplitCategory,SplitUsage,TimeSplit, andNOTE_LIMIT.quest usageand--jsongain the split lines and asplitsarray.quest records sendernow asks models.Validation
just checkandjust testpass: 1360 tests, 5 skipped, after merging feat(records)!: record what each merged PR broke #726. Newtest/split.test.tscovers Claude and Codex parsing (usage deduped per reply, Codex running totals), label validation (gaps, overlaps, out-of-range,idle, long notes), the split's sums with overlapping waits, incremental labeling, relabeling after the transcript changes, checker sampling, andparseRecord's split rules. New tests in send, usage, and review cover the upload carrying the split, a labeling failure keeping the queue, the report lines, and the no-tools flags.askandlabelSession. Each category's time summed exactly to the transcript span, and each one's tokens to the transcript total. Each part cost about 25k input and 0.5 to 0.8k output tokens. The notes came back generic, quoting nothing.--skip-git-repo-check.rework/waitingandunderstanding/testing.claudereports "Not logged in" (it routes throughANTHROPIC_BASE_URLwithout a credential in the environment), so every real run above fell through togpt-6.1-sollow and medium, exactly as the ranked lists intend. The 16% therefore compares Sol low with Sol medium, not Haiku with Sonnet. token-max is not installed on this host either, so the sender itself ran only in tests.Caused by
No single PR: the time split is new work. The Codex apps exposure is as old as Codex reviewers in
quest review; it surfaced because Codex added connected apps, not through a change here.Follow-ups
Time by roleshows whether the category list or the per-stop cost needs a change.Decisions
ask's error and record them insplit.usage(rejected: label after sending, as a separate pass; price labeling through token-max as records of its own). Done:UnansweredErrorcarries every turn that replied, andlabelSessionadds them for a skipped check and for a part left unlabeled.Models
🤖 Generated with Claude Code
(Written by Claude Opus 5.5 with medium)
Review
Rewritten by
quest reviewafter each round.Head: 1d7a30b
Base: origin/main at 05e59e9
Risk: high,
The required split field changes record parsing for downstream consumers, automatic transcript labeling can spend money and disclose transcript data to models, and reviewer tool restrictions change a security boundary; reverting cannot undo spending or disclosure.Classified: high by Codex, gpt-6.1-sol, low effort, session
01a1d81f-2a11-7930-9f52-30c7a1421bb0Reviewer: Codex, gpt-6.1-sol, high effort
Session:
01a1d7e9-2d4c-7312-93bb-0600b47f5393, round 4Score: 10/10,
The earlier accounting finding is fixed, and reading the incremental changes and merge integration revealed no new issues.Rounds: 4; first round 3 (high 1, medium 2); findings 4 (high 1, medium 3): fixed 4, refuted 0, open 0
Findings: none
The last round's findings, at f41c370:
src/cli/split.ts:508:Preserve paid checker usage when every fallback fails;UnansweredError now carries failed replies' usage, which src/cli/split.ts:524 adds before saving retained labels at line 530; lines 504–511 also persist failed splitter usage for later attempts.Reviewer: Codex, gpt-6.1-sol, medium effort, shadow
Session:
01a1d7e9-2d4c-7ed0-9934-75e1feaff2a7Rounds: 2; first round 2 (high 1, medium 1); findings 2 (high 1, medium 1): fixed 2, refuted 0, open 0