Repository navigation
Consolidate benchmark recipes and cases - #3721
Conversation
# Conflicts: # benches/CLAUDE.md # benches/completion.rs # benches/list.rs # tests/helpers/wt-perf/src/lib.rs
worktrunk-bot
left a comment
There was a problem hiding this comment.
The consolidation reads cleanly — the semantic FixtureRecipe catalog, the atomic-publish + file-lock for the pinned source, the jq replacement for the Python converter (faithful to the old bench/mean_ns/stddev_ns shape), and the docs all line up, and dropping the round/Consumed-repair machinery in favor of a fresh-per-iteration live fixture is a nice simplification. One blocker before this can land:
nix-flake fails to compile — benches/large-repository-fixture is filtered out of the Nix build source. The new include!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../../benches/large-repository-fixture")) in wt-perf's lib.rs pulls in a file with no .rs extension (deliberately, so Cargo's bench auto-discovery doesn't treat it as a harness). But flake.nix's source filter keeps only what craneLib.filterCargoSources accepts (.rs/.toml/lock) plus a few named exceptions, so the extensionless fixture never reaches the sandbox and worktrunk-clippy (--all-targets, which builds the wt-perf lib) dies with:
error: couldn't read `/build/source/tests/helpers/wt-perf/../../../benches/large-repository-fixture`: No such file or directory (os error 2)
This is the same class of problem already handled for gemini-extension.json: add the fixture to the filter clause in flake.nix (the filter = p: type: ... block), mirroring the existing exception —
|| (baseNameOf p == "gemini-extension.json")
|| (baseNameOf p == "large-repository-fixture");Renaming the file to .rs would be the other option, but then Cargo would auto-discover it as a [[bench]] target and fail on the missing harness — so the filter clause is the cleaner fix. Happy to push that one-line change to the branch if you'd like.
|
Approved on the Two checks I didn't see settle before my polling window closed, so they're unverified rather than green: |
…ion (#3722) Daily review of the 2026-08-02T08:51Z → 2026-08-03T08:51Z tend window. 8 runs failed, all with the same root cause, and one of them left a PR permanently un-reviewed. This adds the recovery step that was missing. ## What happened Every failure in the window was `claude -p exited non-zero (exit=1)` at the harness step, across three workflows — `tend-notifications` ×5, `tend-review` ×2, `tend-nightly` ×1. The session logs give one cause for all eight: ``` {"model":"<synthetic>","stop":"stop_sequence", "content":["You've hit your session limit · resets 8:30am (UTC)"]} ``` Two 5-hour subscription windows were exhausted (resets at 03:10Z and 08:30Z). That is quota, not a bug — no fix PR is warranted for the exhaustion itself. The consequence is the problem. The harness recorded all eight in the `tend-outage` issue [#3715](#3715), one row each with the run link and the triggering `#N`, and then nothing re-ran them. `tend-review` fires only on `pull_request_target`, so a review that dies at turn 1 never retries. [#3712](#3712) recovered by luck — a later push re-triggered it. [#3721](#3721) did not: at analysis time it was the only non-bot open PR in the repo with zero `worktrunk-bot` reviews, its head unchanged since 06:06Z, and nothing was going to re-trigger it. I re-ran [30789110268](https://github.com/max-sixty/worktrunk/actions/runs/30789110268) manually (attempt 2) to unblock it. ## Why codify it This isn't a one-off. "Bot temporarily unavailable" issues recur about weekly — [#3715](#3715), [#3632](#3632), [#3586](#3586), [#3260](#3260), [#3153](#3153), and ~15 more back to April. The failure is structural: a rate-limited run always dies at turn 1, always leaves its trigger unserviced, and there is no decision point where the bot could have done better. It's also invisible — the PR just sits there looking reviewed-by-nobody, and the last two review windows both recorded "0 failed runs", so the log gave no warning either. ## The change One section in `running-tend`, placed next to the existing CI-fix classification guidance: - Drain the open `tend-outage` issue during the daily `review-runs` sweep and extract the stranded run IDs and triggers. - Re-run only event-triggered runs — `tend-nightly` and `tend-notifications` recover on their next cron tick. - Check the PR still lacks the work before re-running, so a trigger that already recovered via a later push doesn't burn quota twice. - Diagnose the cause from the session log rather than the issue body, which says only "The bot failed to process a request". Both recipes were run against this window's data before being written down: the outage-issue drain returns all 8 run IDs plus `#3712` / `#3721`, and the session-log `jq` returns the session-limit line from the downloaded artifact. <details><summary>Window detail</summary> **Spend**: $49.74 API-list across 52 token-bearing runs (7.7K in / 304.6K out / 2.5M cache-create / 31.2M cache-read). Per workflow: `tend-review` $35.16 (29 runs, 15 distinct PRs), `tend-weekly` $4.64 (1), `tend-notifications` $4.34 (10), `tend-mention` $5.60 (11). Down from $86.04 the prior window, partly because eight runs died at turn 1 for $0. No waste outlier — the review spend tracks real PR throughput. **Outcomes**: 3 bot PRs merged ([#3706](#3706), [#3707](#3707), [#3708](#3708)), 0 closed without merge, 0 maintainer corrective comments. `tend-nightly` [30793479820](https://github.com/max-sixty/worktrunk/actions/runs/30793479820) died at turn 1, so this window's nightly sweep produced nothing — it recovers on tomorrow's cron. No job ran long (max 4 min among failures; nothing near the 360-min cap). **Notifications cadence checked, not a problem**: `*/15` gives ~36 runs/day, but only 10 reached the harness — the workflow's pre-Claude notification gate exits the rest before any token spend. ~6 substantive runs/day at ~$0.87. Not the quota driver. </details> --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
…innable (#3736) Daily `review-runs` sweep of the 2026-08-03/04 window. One maintainer correction in the window is generalizable repo knowledge, so it gets codified here. ## What happened Triaging [#3696](#3696) ("Allow selecting the `wt list` JSON schema on the command line"), the bot read [`resolve_json_schema`](https://github.com/max-sixty/worktrunk/blob/286004f1ebe5f6563c772be24987394144a29215/src/commands/list/mod.rs#L220), saw it reads `repo.config().list.json_schema`, and [posted](#3696 (comment)) that "there's no way to pin it per-invocation independent of that config" — then went further and said the usual alias deflection "can't substitute, since there's no composable way to select the schema without this flag existing first". max-sixty [corrected it](#3696 (comment)): "the need is real, but it's already met, so I'm not going to add the flag" — `wt --config-set 'list.json-schema = 2' list --format=json` pins it per invocation, and `WORKTRUNK_LIST__JSON_SCHEMA` does the same a layer down. The proposed `--schema` flag was declined as "a third spelling of the same thing on a surface I deliberately keep small". The issue was closed on that basis. `--config-set` has been documented since [#3138](#3138) (2026-06-19), in `after_long_help` in [`src/cli/mod.rs`](https://github.com/max-sixty/worktrunk/blob/286004f1ebe5f6563c772be24987394144a29215/src/cli/mod.rs#L2558) and its rendered mirror. It was available to read; the triage just never looked above the resolver. ## Why codify it rather than log it The specific miss is what the gates would call a single occurrence, but the shape isn't a behavioural nudge that risks noise elsewhere — it's a factual property of worktrunk's config layering, and the failure mode it prevents is a **publicly posted false capability claim**, which is the class `running-in-ci` treats as the most corrosive output failure. It's also invisible: nothing fails, the reply just reads as authoritative and wrong until a maintainer happens to catch it. It sits directly above **Suggesting Aliases for Niche Feature Requests** because it serves the same flag-growth limit that section already encodes, and is the cheaper answer when the ask is "select an existing config value per invocation" — the alias deflection is the fallback when it isn't. <details><summary>Window summary</summary> 46 token-bearing runs, **0 failed**, 0 near-timeout (longest job 68 min against the 360-min cap). 3 bot PRs opened ([#3731](#3731), [#3732](#3732), [#3734](#3734)), 2 merged ([#3722](#3722), [#3726](#3726)), 0 closed without merge. [#3721](#3721) — the PR stranded by the 08-03 outage — was reviewed and approved, confirming yesterday's recovery worked. Full evidence in the tracking issue [#3691](#3691). </details> --------- Co-authored-by: worktrunk-bot <254187624+worktrunk-bot@users.noreply.github.com>
## Problem The gated CI poll in **CI Monitoring** caps at ~9 minutes, but nothing says the cap is the *whole* budget. Two `tend-review` sessions on `max-sixty/worktrunk` have now hit the cap and simply run the loop again — three full passes each, ~28 minutes of post-approval polling — because re-entering reads as diligence rather than as waiting on something that already proved it won't settle. It can't settle. `pending()` deliberately reads the whole `statusCheckRollup` rather than `--required`, so it counts advisory jobs too. worktrunk's `nightly` workflow runs `feature-powerset` and `benchmarks` for 80+ minutes on a PR head — the repo's own overlay documents the benchmark job's runtime — so on any PR that triggers them `pending()` structurally cannot return 0 inside a 9-minute cap, no matter how many times the loop is re-entered. ## Evidence | Run | PR | Approved | Poll passes | Ended | Still pending at exit | |---|---|---|---|---|---| | [31086241862](https://github.com/max-sixty/worktrunk/actions/runs/31086241862) | [#3748](max-sixty/worktrunk#3748) | 08:48:33Z | 3 × 9 min (08:48:41Z → 09:16:19Z) | 09:16:37Z | `feature-powerset` (started 08:49:11Z, still running an hour later) | | [30833412090](https://github.com/max-sixty/worktrunk/actions/runs/30833412090) | [#3721](max-sixty/worktrunk#3721) | 16:56:51Z | 3 passes, ~26 min beyond the first | 17:33Z | `feature-powerset`, `codecov/patch` (never registered) | In run 31086241862 every required check was terminal by 09:00:25Z; the remaining 16 minutes of polling watched one advisory job. The session's own log shows it saw `feature-powerset IN_PROGRESS` on all nine iterations of the final pass and then ended with the reasoning `"One non-required nightly job, feature-powerset, was still running when I hit the polling cap (~28 min) — I didn't wait it out; it's advisory and a failure there couldn't stem from a comment-only diff."` — a sound call, but it stayed in the session log. Step 4 asks for that as a comment; nothing was posted, so the PR carries an approval with no record of what went unverified. The 30833412090 session did post the comment, so the two runs resolved the same state differently. ## Change Step 4 now says the cap is terminal, gives the structural reason (`pending()` counts advisory jobs, so a long matrix can never reach 0), and specifies what the unverified-checks comment should carry — each pending check, marked required or advisory, with `gh pr checks --required` named as the source since the rollup the poll reads carries no required flag. An advisory-only tail becomes one cheap line rather than a judgment call about whether to say anything at all. ## Gate assessment - **Evidence level**: High — consistent across two independent sessions, both traced to the same structural cause. Occurrences: 2 (this run + prior evidence). The prior entry pre-registered exactly this escalation: *"Watch for a second, independent run showing the same re-entry before treating as High."* - **Structural**, not stochastic: `pending()` counts non-required checks by design (the recipe explains why it avoids `--required`), so any repo with a long advisory matrix reproduces this on every gated poll. - **Change type**: targeted fix — one numbered step, no new section. Normal bar, met. ## Not covered elsewhere [#821](#821) addresses the **pre-APPROVE** rollup guard in `review/SKILL.md` (provenance of a stale red when the cap expires); this is the **post-approve** dismiss-on-red poll in `running-in-ci`, where a terminal rule already exists and is being skipped rather than improvised. [#870](#870) adds a subsection after step 4 about reviews landing mid-poll and does not touch the step itself. Evidence log: https://gist.github.com/a88c03f4d0c3fb1791060ff3dd97d1c4 --------- Co-authored-by: tend-agent <270458913+tend-agent@users.noreply.github.com>
Benchmark fixtures had accumulated around individual call sites, leaving the same repository shapes and command modes expressed several ways. This change makes repository state the organizing concept: benchmark groups select semantic
FixtureRecipes, share table-driven cases, and retain separate fixtures only when a controlled contrast, destructive precondition, or disproportionate setup cost requires one.The real-repository list benchmarks now share one pinned
rust-lang/rustfixture with eight worktrees and fifty branches spread across history. The list matrix keeps default, branch, warm, and cold coverage without maintaining several “real” repository handles. Remove and prune cases share the same case machinery, while the destructive large-repository prune state remains separate.The scheduled workflow now converts Criterion estimates directly with
jq, removing the one-off Python converter and its tests. The benchmark guide records the canonical-fixture principle and the remaining recipe-to-group mapping.Tests:
cargo run -- hook pre-merge --yes(4,551 tests);cargo bench --bench list large_repository -- --test;cargo test -p wt-perf; benchmark check, clippy, formatting, and diff checks.