Skip to content

trunk-merge/pr-89870/30164a18-b801-4c70-822b-f27eca17d2f9 - #90049

Closed
trunk-io[bot] wants to merge 54 commits into
masterfrom
trunk-merge/pr-89870/30164a18-b801-4c70-822b-f27eca17d2f9
Closed

trunk-merge/pr-89870/30164a18-b801-4c70-822b-f27eca17d2f9#90049
trunk-io[bot] wants to merge 54 commits into
masterfrom
trunk-merge/pr-89870/30164a18-b801-4c70-822b-f27eca17d2f9

Conversation

@trunk-io

@trunk-io trunk-io Bot commented Aug 27, 2026

Copy link
Copy Markdown
Trunk Merge Pull Request Banner

This pull request was created and is being managed by Trunk Merge.

This pull request is based on the master branch at SHA 7eced17f1684d6461a693c4621acaa75336840e9.

See more details about each PR in the batch here:

When CI completes, this pull request will be closed automatically.

Pull Requests Being Tested

This pull request is testing a batch with the changes from pull requests 89870 and 89869 - batching documentation.

Dependencies

This pull request depends on the changes from pull requests 89886, 89755, 89945, 89958, 90004, 84558, 90010, 83663, and 89868.

arnohillen and others added 30 commits August 17, 2026 12:21
Convert five dispatch ladders (isinstance chains, len()+index probing of
field chains, nested dict.get() probing) to match statements using class
patterns with field capture, or-patterns, guards, sequence patterns, and
mapping patterns. Behavior is unchanged: every branch of each old ladder
maps to exactly one case, in the original order or one proven equivalent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Generated-By: PostHog Desktop
Task-Id: 3cb06508-6ac6-4768-a330-4495ac21001e
Add a Box plot option to SQL insights, gated behind the sql-box-plot-insight feature flag. Maps one row per X-axis and series pair into grouped boxes, auto-maps conventional names for minimum, percentiles, median, mean, and maximum, and keeps percentile calculation in SQL instead of using limited browser results.

Adopted from PR #84558. Rebased onto master to resolve merge conflicts and squashed into a single commit.

Generated-By: PostHog Desktop
Task-Id: f1e69bea-f4e6-44cf-bd12-dc81f8a435c3
Extract the repeated single-Y-axis boolean into a named isSingleAxisChart flag in the display tab, and reuse one height-class expression for the box plot chart's empty, error, and success states. No behavior change.

Generated-By: PostHog Desktop
Task-Id: f1e69bea-f4e6-44cf-bd12-dc81f8a435c3
Leave Enter to the hosted survey textarea and rely on the survey SDK Cmd/Ctrl+Enter shortcut for submission. Keep the editor preview hints aligned with respondent behavior.

Verified with the external survey backend suite, frontend typecheck, CI preflight, and focused Oxlint and Oxfmt checks.
6 updated
Run: 15e93842-9e54-48e9-b856-6eced8bb9c0e

Co-authored-by: pauldambra <984817+pauldambra@users.noreply.github.com>
Return the raw series label when a trends result does not include action metadata.

Generated-By: PostHog Desktop
Task-Id: ca36deeb-2df0-4f9b-9d48-ed2f3e987a9a
2 updated
Run: 6bf474dd-8b3a-4964-97a9-bafbe2282a55

Co-authored-by: sampennington <56024559+sampennington@users.noreply.github.com>
CI ideas get re-proposed because the evidence against them is scattered
across closed PRs and revert commits nobody greps. The same handful keeps
coming back: xdist in the shards, BuildKit cache mounts, sharding the E2E
suite, coverage-based test selection.

Adds docs/internal/ci-things-already-tried.md, keyed by the proposal rather
than the symptom, because the lookup happens when someone has an idea, not
when they are debugging. Each entry carries the verdict, the date, the
measurement, and a line of alternate phrasings so a differently-worded grep
still lands.

Verdicts are not bans. Every entry states the specific blocker so a reader
can check whether it still holds; runner sizes and tooling move.

Seeded from Julian's merged, reverted, and abandoned PRs since Oct 2025.
Narrowed with local git: revert titles, plus lines added in one PR and
removed in a later one across the CI hot files. That cut 847 PRs to 333
candidates, then to the entries here.

The five CI skills that would otherwise carry duplicate copies get a
one-line pointer instead.
…glish

The first draft read like PR prose: idioms, passive constructions, and long
sentences that carry two ideas. Agents and non-native speakers both parse
that worse, and this file exists to be scanned fast during a lookup.

Rewrites to ASD-STE100 writing rules while keeping full vocabulary: active
voice, simple tenses, one idea per sentence, consistent terms for the same
thing. Now averages under 10 words per sentence with none over 25.

No entries, verdicts, or PR links changed.
… doc

Paul's original ask in #team-devex named three areas: CI, django perf, and
tests. The first pass covered CI and tests only.

Adds a Django performance section that points at django-startup-time.md as
the deep source, plus the three proposals that keep coming back: squashing
the migration history (#48267, effect on timing too small for the blocker
work), pydantic defer_build (reverted, relocated cost onto the first query
after a deploy), and dropping pydantic from the generated schema (250+
model_validate callers make it non-local; the import cost was solved by
evicting the module from django.setup instead).

Also adds the Blacksmith runner trial (#54559 to #57991). Nothing in the
repo recorded why the shadow existed or why it went away, and
compare-ci-runners.py is still sitting there marked legacy.

Vendor benchmark numbers stay out. They are unpublished internal
measurements of a named third party, and the trial's own conclusion was
that cache-state asymmetry, not runner speed, drove the large gaps.
An agent recently read CI, found no pytest-snob in pyproject.toml, and
concluded the selector was dead. It isn't: pytest-snob is an inline PEP 723
dependency of tools/snob_backend_test_selection_shadow.py, so it never
appears in the dependency file.

The narrow scope added to the confusion. Selection ran on drafts only for a
while, deliberately, until the merge queue was stable. #85530 extended it to
ready PRs and #88265 folded Django and product selection into one job.

This is the exact question the doc exists to answer, so it gets an entry
rather than a correction in a thread.
PRODUCT_SAFETY_FACTOR was a single 1.3 covering two unrelated risks.

On a packed bucket the products run sequentially, so the job's wall is the
sum of its parts and there is no mean-versus-max gap to cover. The 30%
markup there only bought extra buckets, each paying the base overhead
again. On a split product the factor is load-bearing: sizing solves for the
mean shard while the run's wall is the max, and nothing else bridges that.

Split it in two. Buckets get 1.1, covering durations-map error alone.
Splits get a margin interpolated from the measured max/mean at p90, which
rises with the shard count because the max is an order statistic over more
shards. Resolving that needs a fixed point, seeded at no margin so a
product that fits one shard is not split in two by its own headroom.

Product test jobs now carry legs, one turbo invocation each with its own
pytest-split flags, so a split product's last shard can share a job with
whole products without --splits/--group leaking onto them. The packer can
seed from those remainders. It does not fire yet: with the margin reserved
on every shard there is almost no modeled spare, and the real spare, a
trailing-shard taper, is not something the sizer can see today. Landing the
mechanism now keeps it inert until the margin drops.

DEDICATED_BUCKET_PRODUCTS drops batch-exports. It was listed for an
async-fixture teardown hang; it now runs at the median product failure rate
with no job near the timeout, and the branch was unreachable regardless
since the split path is checked first. The set stays as the extension
point, with the criterion for re-adding written down.

Also corrects the end-to-end claim in the sizing comment. The pre-shard
preamble measures about 5.5 minutes, so a 12-minute shard target puts a
full PR run near 19 minutes, not 15.

Claude-Session: https://claude.ai/code/session_01EJ1y3UzXR1BKtukE886ks9
The first pass filtered closed PRs to those with a closing comment over 60
characters, which covered 34 of 125. That threw away the stale-bot closures,
where the body still records exactly what was tried and why it stopped.

Adds the ideas that get re-proposed most: Tilt over mprocs, sharing the dev
environment across worktrees (three separate attempts), sparse-checkout on
the big CI workflows, moving ee/ into products/, session-scoped
django_db_setup, and six unmerged attempts at validating responses against
the OpenAPI schema.

Two existing entries were wrong by omission and are corrected. BuildKit
cache mounts were rejected in bulk, then adopted narrowly for uv, which then
cached wheels built against a different libxmlsec1 and broke the image; the
fix that stuck puts the library version in the cache ID. Migration squashing
had a second attempt on a different design, and that one also stalled.

Both corrections came from checking present-day state rather than trusting
the verdict in the PR, which is the habit the doc asks readers to have.
…e saga

Ran a per-entry check of all 32 entries against PR metadata, PR bodies and
comments, and present-day repo state. 29 held up. Three did not, and all
three failed the same way: the PR was trusted over the repo.

- The Tilt entry claimed bin/mprocs.yaml is still the process list. It is
  not. bin/start defaults to phrocs, which landed in #50341 back in March
  and reads a config generated per run by hogli dev:generate.
- The SeaweedFS entry dated the first attempt to Mar 2026. The real first
  attempts were Sep and Oct 2025, the replay store migration merged in Nov
  2025, and the PR that actually shipped the objectstorage swap (#60432)
  was never cited. The one PR it did cite never merged.
- The xdist entry quoted 42.8%. No source says that. The closing comment
  gives rounded wall times, so the entry now gives those.

Two claims were mine rather than the record's, and are marked or removed.
The Blacksmith trial's duration and its cache-asymmetry conclusion are not
in either PR, so the entry now says the detail lives in a team-channel
report it cannot show. The sparse-checkout entry stated a rejection reason
that nobody ever wrote: no human reviewed that PR, the stale bot closed it.
It also described a diff that touched three workflows when it touched two,
and missed that ci-rust.yml already used sparse-checkout beforehand.

Removes Tilt, SeaweedFS, and Granian. None for being old. Tilt's subsystem
was rebuilt around it, and the other two shipped, so neither failure is a
live trap. Adds a "Remove an entry" section so the next removal has a rule
to cite. The oldest entry in the file, xdist from October, stays: it is
still the most re-proposed idea here.

Adds the person-table cutover, eight unmerged PRs from November trying five
mechanisms to repoint the Person model at a partitioned table. Person
access now goes through the personhog client, so a Django-level cutover is
no longer where that problem lives.
The timing workflow has failed on every run since .test_durations left the
repo. That PR stripped the commit machinery from this workflow but left a
`git checkout -- .test_durations` behind, and the file is now untracked and
gitignored, so the step errors:

    error: pathspec '.test_durations' did not match any file(s) known to git

Consequence: no map has been published since. Every consumer has been
planning against a cache entry from before the duration floor was removed,
so that fix has never actually reached CI. Drop the checkout, and guard the
copy — the cache restore is best-effort and there is no tracked file behind
it any more.

With an unfloored map in hand the fitted split margin is wrong. It was
calibrated against shards planned on a floored map, where tens of thousands
of tiny parametrized tests carried ten times their real weight and the
partition only looked balanced. Replaying the real map through the splitter
puts max/mean at 1.02, not the 1.12-1.32 the table assumes, so the table
would now buy shards nothing needs.

Derive it instead. pytest-split cuts between tests and never inside one, so
a contiguous split's worst chunk runs at most one whole test past the mean.
Sizing the worst chunk rather than the mean gives

    work / n + maxTest <= budget   ->   n = ceil(work / (budget - maxTest))

which needs no fitted constant, no fixed point, and tracks the map rather
than a snapshot of it: a product carrying one heavy test gets the shards
that test forces, an evenly grained one gets none it does not need. A test
at or above the budget is called out on its own — no split can hold the
target then, so size by work alone instead of buying shards that cannot
help.

Against the current map this sizes the split products at 22 shards where
they run 27 today, and stops splitting signals, whose whole suite fits one
shard.

Claude-Session: https://claude.ai/code/session_01EJ1y3UzXR1BKtukE886ks9
The drift check compares the depot shadows against canonical, so the legs
loop and the untracked-map guard have to land in both.

Claude-Session: https://claude.ai/code/session_01EJ1y3UzXR1BKtukE886ks9
Review of the previous commit found the drift fallback still assumed the
previous map is always on disk. Tolerating a cache miss in "Set the previous
map aside" opened a path where /tmp/previous_durations does not exist, and
the fallback's jq then aborts the whole processing step under bash -e, so
Core, Temporal and Dagster never publish either. Losing the product entries
for one refresh is the smaller failure, so fall back to an empty slice and
keep the rest.

A product test job now refuses to pass when its matrix entry carries no
legs. An unset or malformed entry left the loop unentered and the job green
having run no tests, which the single-command form could not do, and the
rollup gate only reads this step.

Three comments were left describing behaviour the code no longer has: the
split path claiming a safety factor it no longer applies, a restore step
claiming a cache miss leaves a checked-out file, and a packing note claiming
optimal_chunks leaves the last shard lightest. The algorithm splits its
heaviest group to reach the requested count, so that last claim was never
true; the packing is sound because work/shards + maxTest bounds every shard,
which the comment now says instead.

Claude-Session: https://claude.ai/code/session_01EJ1y3UzXR1BKtukE886ks9
The mean-plus-one-test bound stops being informative once a single test eats
most of a shard's budget: the denominator collapses and the count runs to the
cap. A product with 321 seconds of work and one 319-second test asked for 50
shards where 3 hold the target. Below a quarter of the budget, size by work
and add the one shard that carries the heavy test.

The bucket factor's comment described the shared factor the sizing used
before, which is history rather than a reason and goes stale as the model
moves. It now states the invariant alone: the bucket factor covers error in
the recorded durations, and the split path derives its own headroom.

Claude-Session: https://claude.ai/code/session_01EJ1y3UzXR1BKtukE886ks9
A workflow edit reaches every open PR the moment it merges, while a change to
the script beside it only arrives when the branch rebases. Reading matrix.legs
and nothing else made the new workflow require a key those branches cannot
have, and the guard added with it turned that into a hard failure before any
test ran.

The workflow now normalizes a missing legs value into a single leg built from
the flat filters and pytest_args pair, so an unrebased branch runs the
invocation its own turbo-discover planned. The script keeps that pair beside
the leg on every entry one invocation can express; an entry with several legs
has no such form, and by the time one exists the workflow reading it is the
new one.

The guard stays for the case where neither shape is present, which would pass
the job having run no tests.

Claude-Session: https://claude.ai/code/session_01EJ1y3UzXR1BKtukE886ks9
Sizing read a product's longest test and assumed the rest could pack around
it. That holds for one heavy test and breaks for several: ten tests just
under the budget were sized at nine shards, so two of them shared a chunk and
ran to twice the target.

Read the distribution instead. Tests above half a shard's budget cannot pair
with each other, so they are counted rather than summed and set a floor no
packing goes below. The remainder is at most half a budget per test, so a
contiguous chunk of it runs at most one of them past its mean, which leaves
n = ceil(lightWork / (budget - maxLight)). That denominator cannot fall below
half the budget, so the count no longer runs to the cap when one test sits
just under the budget either.

Against the current map no product carries a test that large, so the real
shard counts do not move.

Claude-Session: https://claude.ai/code/session_01EJ1y3UzXR1BKtukE886ks9
Sizing counted a heavy test as taking a shard and left the rest to pack
around it, but the cuts are contiguous: a heavy test between light tests
divides the light run instead of lifting out of it. Ordered durations of 53,
301 and 133 seconds sized at two shards, and either cut leaves a chunk of 354
or 434 seconds against a 320-second budget.

H heavy tests leave at most H + 1 light runs and each run rounds up on its
own, so the light side can cost H shards beyond its own bound. Charge that
whenever any light work exists. This is the worst case rather than the actual
one, because the sizer reads the map and not collection order, and it only
costs shards on a product holding a test above half the budget.

Against the current map no product holds one, so the real shard counts do not
move.

The dedicated-bucket comment kept a record of why batch-exports was once
listed. That is operational detail with a shelf life; only the condition for
belonging in the set stays.

Claude-Session: https://claude.ai/code/session_01EJ1y3UzXR1BKtukE886ks9
The two billed replay meters keyed on $snapshot_source and $lib, both of which
arrive from the client unvalidated. Combinations outside the expected set matched
neither meter. Web is now the catch-all for the source, and the SDK no longer
takes part in the mobile meter, so the two partition every session.
Standing in an empty products slice when the cache missed let the refresh
succeed and publish. That map becomes the newest cache entry and the latest
successful artifact, so it shadows the older valid one and every consumer
drops to the file-count estimate, under-sharding the fixture-heavy products
until a later refresh lands. Fail the refresh instead and leave the older map
newest. Reaching that needs both a cache miss and a Products slice that fails
its drift check, unlike the unconditional checkout this branch started from.

Shard sizing also charged for a fragmentation the suite may not have, and the
charge could ask for more shards than the product has tests. A shard past that
point collects nothing and spends a runner without shortening the critical
path, so the count is capped at the test count.

Claude-Session: https://claude.ai/code/session_01EJ1y3UzXR1BKtukE886ks9
The floor ran after the cap and undid it, so a product holding one test that
overruns the budget planned two jobs. The second is guaranteed to collect
nothing, and no split shortens the first, so cap the floor itself.

The stale fallback also dropped the recorded heavy tests and re-estimated
everything as five-second light work. Those recordings are measurements even
where coverage is poor, and a test above half the budget holds a shard
whatever the file count says. Keep them, and treat only the guessed remainder
as light.

Claude-Session: https://claude.ai/code/session_01EJ1y3UzXR1BKtukE886ks9
TueHaulund and others added 24 commits August 27, 2026 13:09
2 updated
Run: 4591d94f-63cd-4f14-b77b-0701f885d6ba

Co-authored-by: hpouillot <3455883+hpouillot@users.noreply.github.com>
The Person-table entry said `PERSON_TABLE_NAME` is not in the code. It is:
#41620 merged from the same window and carried the setting to master, where
`Person.Meta.db_table` reads it. The entry steered readers away from a switch
that already exists. What did not land is the test setup and dual reads around
it, so the entry stays and now says that.

The xdist entry drew a 3-minute conclusion from a 6-minute measurement. The
closing comment on #38927 reports ~15 min to ~9 min.

The Blacksmith entry paraphrased a conclusion that lives only in an internal
report. This repo is public, so the entry now carries the outcome the PRs show
and stops there.

Sparse checkout was labeled `rejected`, which this file defines as built and
measured. Nobody reviewed that PR and the stale bot closed it, so `abandoned`
is the verdict the taxonomy already has for it.

Pointers went only to five CI skills, so a migration, endpoint, isolation, or
stacking proposal never saw the sections written for it. Four more skills get
the same one-line pointer.

Claude-Session: https://claude.ai/code/session_011D2GDLMQGPxQLEkHxevuRs
Make severity controls, filters, and settings available without the rollout flag. Remove the retired flag constant and update affected tests and stories.

Generated-By: PostHog Desktop
Task-Id: e5e0d821-a9c7-4b96-9ade-55c378a59fbc
8 updated
Run: be75d635-fd5e-43e9-9022-76cd056ea9f0

Co-authored-by: hpouillot <3455883+hpouillot@users.noreply.github.com>
4 updated
Run: 40e5c001-3352-49ba-9030-f30f9f5ebde8

Co-authored-by: hpouillot <3455883+hpouillot@users.noreply.github.com>
@trunk-io trunk-io Bot closed this Aug 27, 2026
@trunk-io
trunk-io Bot deleted the trunk-merge/pr-89870/30164a18-b801-4c70-822b-f27eca17d2f9 branch August 27, 2026 12:13
@trunk-io

trunk-io Bot commented Aug 27, 2026

Copy link
Copy Markdown
Author

Static BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

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.

7 participants