Skip to content

Consolidate benchmark recipes and cases - #3721

Merged
max-sixty merged 5 commits into
mainfrom
codex/canonicalize-benchmarks
Aug 5, 2026
Merged

max-sixty merged 5 commits into
mainfrom
codex/canonicalize-benchmarks

Conversation

@max-sixty

Copy link
Copy Markdown
Owner

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/rust fixture 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.

This was written by Codex on behalf of max-sixty.

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@worktrunk-bot

Copy link
Copy Markdown
Collaborator

Approved on the flake.nix fix — nix-flake is green on c51e381, along with test (linux|macos|windows), lint, msrv, minimal-versions, and code-coverage.

Two checks I didn't see settle before my polling window closed, so they're unverified rather than green: feature-powerset (still running) and codecov/patch (never registered a check run on this head, though the code-coverage job itself passed).

max-sixty pushed a commit that referenced this pull request Aug 3, 2026
…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>
max-sixty pushed a commit that referenced this pull request Aug 5, 2026
…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>
@max-sixty
max-sixty merged commit 970976b into main Aug 5, 2026
45 checks passed
@max-sixty
max-sixty deleted the codex/canonicalize-benchmarks branch August 5, 2026 17:30
max-sixty pushed a commit to max-sixty/tend that referenced this pull request Aug 11, 2026
## 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>

This branch was previously deployed

1 inactive deployment
tend — c51e381a Deployed Aug 3, 2026 by max-sixty via review #3693
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.

2 participants