Skip to content

fix: an oversized PR body is shortened, never a lost run (UPT-106) - #227

Merged
lucasbraum merged 2 commits into
mainfrom
upt-106-pr-body-budget
Oct 18, 2026
Merged

lucasbraum merged 2 commits into
mainfrom
upt-106-pr-body-budget

Conversation

@lucasbraum

Copy link
Copy Markdown
Collaborator

Closes UPT-106. Closes UPT-102: enki now delivers an agent-led draft, uptide-bench/entur--enki#5.

Problem

prBody() threw "Required migration evidence exceeds the PR body budget" when the rank-0 sections alone were over the budget. That threw away the whole finished run. It happened on enki (a8ff50ca, after #224) and on dms-saas (#221).

Change

  • No section is exempt. fitPieces returns undefined instead of throwing. Required lists that can grow without bound get a compact form, which is used only when the whole list does not fit. These lists still go before anything droppable.
    • Lockfile churn (outside the upgrade, and parent churn): counts per kind of change (added, removed, version changed, re-resolved at the same version), then the first 10 packages.
    • Manual sites under "Worth a look": grouped by rule, with a count and the first 5 locations each.
    • Remaining type errors: the first 10, then a count.
  • Full lists stay in report.json. The Action's job summary now lists every breaking site (it was capped at 50). Compact forms link to it through a new runUrl field, set from GITHUB_SERVER_URL/GITHUB_REPOSITORY/GITHUB_RUN_ID when the run is in Actions.
  • Minimal body. If the compact form still does not fit, the body is a fixed-size summary: the verdict, the checks (types, tests, builds, lint, clean install), the counts (sites by outcome, lockfile, files, decisions), the branch and commit, and where the full report is. It opens with a "Shortened description" notice.
  • The run is kept. fix() and the isolated run's finish() write report.json before the body, and nothing throws for size any more, so a finished run always keeps its commits and report.json. uptide pr sends the shortened body instead of refusing.
  • Decision lines (second commit, found by the enki rerun). An agent decision named every file in its task's scope. The verification-feedback task was scoped to 317 files, so its 2 decisions came to 46 KB. A decision now names the files the task changed, five at most, and counts the rest.

Fixtures

Written first; both failed on main with the throw.

  • 300 manual sites (3 rules) and 100 + 100 churned lockfile entries. At the 60 k budget every section is present: lockfile sections, risk table, What changed, Worth a look, Decisions, Verification details, Run details. Manual sites are compact and the lockfile lists are whole. At 8 k the lockfile lists are compact too: "100 entries: 25 added, 25 removed, 25 re-resolved at the same version, 25 version changed", with the first 10 packages.
  • The same report with an oversized decision renders the minimal body.
  • A 317-file task with one changed file: the decision names only that file. Without diffs it names 5 files and "312 more files".

enki rerun (#169 harness)

Uptide arm only, claude-sonnet-5-5, default $1 cap, one run: 60949226.

Exit 0, draft delivered (branch uptide/react-19.2.0, verified commit 3f05f25)
PR body 804 chars: minimal fallback (on 06bab33). With 16b2abe the same stored report renders whole at 34,718 chars (77,681 before), no shortening
Checks (harness) install 0 · typecheck 0 · lint 0 · test 0
#220 clean install npm ci --ignore-scripts --no-audit --no-fund passed
Sites 3 by rule, 8 by the agent, 1 manual; verification PASS, 0 new type errors
Cost / wall time $0.17 (estimate $0.54) · 160 s
Fork CI uptide-bench/entur--enki#5: npm ci, npm run check and tests passed. The job fails at SonarQube Scan (no SONAR_TOKEN in the fork), so build is skipped. Fork PRs #1–#4 fail the same way

The run used the build of 06bab33. The fork PR's body is therefore the minimal form; the 34.7 KB figure is a $0 re-render of the stored report.json with 16b2abe. The report records uptideDirty because the harness files under bench/ were copied in untracked, as in earlier reruns.

Left as is

  • Lockfile churn is not in the job summary. The run-report schema (uptide.run/v1) has no lockfile field, so the full churn list is only in report.json.
  • ReportedError and PR_BODY_TOO_LARGE stay. Nothing throws PR_BODY_TOO_LARGE any more, but fix --json still uses ReportedError for reporting any failure after report.json is written.

🤖 Generated with Claude Code

lucasbraum and others added 2 commits October 17, 2026 23:12
prBody() no longer throws for size. Required lists that can grow without
bound get a compact form, used only when the whole list does not fit:
lockfile churn as counts per kind of change plus the first 10 packages,
manual sites grouped by rule with counts and the first 5 locations, and
remaining type errors cut to the first 10. Full lists stay in
report.json, and in the Action's job summary, which now lists every
site. Both are linked from the body (runUrl). When even the compact form
does not fit, the body is a fixed-size summary (verdict, checks, counts,
where the full report is) that says so at the top.

fix() and the isolated run's finish() write report.json before the
body, and nothing after a finished run throws for the body's size, so
the commits and report.json are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
…pe (UPT-106)

The enki rerun's body overflowed in "Decisions for you". The verification
feedback task's scope was 317 files, and each of its 2 decisions listed
all of them (46 KB). A decision line now names the files the task
changed (its diffs, or its scope when none are retained), five at most,
and counts the rest. enki's stored report renders whole at 34.7 KB
(77.7 KB before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
@lucasbraum
lucasbraum merged commit ae175a9 into main Oct 18, 2026
28 checks passed
@lucasbraum
lucasbraum deleted the upt-106-pr-body-budget branch October 18, 2026 06:40
@uptide-release uptide-release Bot mentioned this pull request Oct 18, 2026
lucasbraum pushed a commit that referenced this pull request Oct 18, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and publish to npm
yourself or [setup this action to publish
automatically](https://github.com/changesets/action#with-publishing). If
you're not ready to do a release yet, that's fine, whenever you add more
changesets to main, this PR will be updated.


# Releases
## uptide@0.15.1

### Patch Changes

- [#220](#220)
[`cf86d65`](cf86d65)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - With an assisting
agent, a root pin Uptide cannot update safely (a nested override such as
`overrides > pkg > peerDependencies > react`) no longer stops `fix` with
exit 2: the run continues as an agent-led draft that leaves the pin as
written and names it, with its exact path and value, at the top of the
PR body. The agent-led peer reason now names each blocker's first
release that accepts the target, or says none does. An agent-led draft
must pass the repository's clean install (`npm ci` or the manager's
frozen install) before delivery; if it does not, the PR says so at the
top. Without an agent, with `--no-llm`, or on a safety refusal, `fix`
still exits 2.

- [#232](#232)
[`e4a2885`](e4a2885)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `uptide fix`
spends less wall time on test runs and dependency copies:
  
- The agent's own `run_tests` check runs only its runner's related tests
when the `test` script builds first and then runs a script that is just
the runner (`pnpm run build && pnpm run test:unit`). The gate after the
agent's edits and the final verification run exactly what they ran
before.
- The upgrade's install moves the installed `node_modules` out of its
temporary worktree instead of copying it.
- Each phase of the fix report lists the repository's commands it ran
(installs, generators, builds, type-checks, tests, lint, format) with
their count and summed wall time. An agent-led run's clean install has
its own phase.

- [#223](#223)
[`2dc2162`](2dc2162)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `fix` no longer
stops with "baseline tests modified the working tree" when the test run
only writes new untracked files, such as a junit `test-report.xml` that
is not gitignored. Those files are recorded as test artifacts
(`testArtifacts` in the report), kept out of every commit, the agent's
diff and the pending-changes check, and removed before each later test
run, `uptide verify` included. A change to a tracked file during
baseline tests still refuses the run.

- [#215](#215)
[`abddcb3`](abddcb3)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `check` no longer
lists possible impact (type changes nothing confirmed as breaking) on
its first screen: each package says it in one line, `+N possible impact
in M files, not counted as breaking`, and `--details` and `--json` still
have every site. A package whose types were partly verified no longer
repeats its coverage line in the heading, and a monorepo's unbuilt
workspace chains are one count on that line.

- [#213](#213)
[`2de3a18`](2de3a18)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - A companion pack
that moves with the lead's fix (react beside next) now gets its own scan
context and inventory, and the PR body carries a section with its review
notes.

- [#212](#212)
[`733cfbe`](733cfbe)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `pack test
--write` records how many breaking sites a finding landed on at any
severity and as breaking, out of all breaking sites it scored, and the
pack tables show both ("Found, any severity", "Found as breaking") in
place of the single recall column. Packs recorded before it show `—`.

- [#224](#224)
[`09f68df`](09f68df)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `fix` on npm no
longer stops when the lock-only install hits ERESOLVE from the old
lockfile. It retries without the lockfile entries of the moved packages,
their companions and the packages whose peers they satisfy, and only
then with the whole lockfile re-resolved; the report says which step
worked, and the lockfile check reports the churn as usual. It never uses
`--legacy-peer-deps` or `--force`. When every step fails, `fix` ends
blocked, naming the conflicting packages from npm's ERESOLVE report.
`fix --json` now prints JSON on every exit, a failure included; when the
run already has its report (a PR body over GitHub's size limit), that
report comes with the error.

- [#221](#221)
[`ff8de8e`](ff8de8e)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `fix` no longer
rejects an agent patch for test files the parent commit already failed,
whichever test scope ran them: when the test script builds first and the
build failed on the error being fixed, the start had no test results to
compare with, so a correct patch was reverted every round. The patch's
tests also run with the pack's follow-ups in place (a test asserting the
old Stripe API version), which `fix` still applies and reports after the
agent. When a PR body cannot fit, the stored report.json the error
points to is now written first. `check` and `fix` now also analyze files
that reach a package's wrapper through a barrel (`import {
getStripeClient } from '../stripe'` with `export * from './client'`).
Edited files are formatted with oxfmt and dprint as well as biome and
prettier, from the workspace that holds them, with its own
configuration, else the root's. The stripe pack adds `request-options`:
request options passed in the params slot (`subscriptions.cancel(id, {
idempotencyKey })`), which SDK 22 rejects, move to the third argument.
It runs assisted until a ground-truth repository exercises it.

- [#226](#226)
[`448c3cb`](448c3cb)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `uptide fix`
judges consumed workspace builds on the final head, not on the tree
right after the bump. A build that failed after the bump and passes once
the edits land no longer starts a feedback round, adds an "Agent-led
draft" reason or shows "Build failed" in the PR body. The builds run
again only when the tree changed since their last result, and not at all
when the verification build already built those workspaces. The fix
report's `buildRuns` gives the number of builds the run executed and
their wall time.

- [#218](#218)
[`13e343b`](13e343b)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `check` folds a
compile error into the breaking site it comes from when the compiler
traces its value there (an `errors.map(e => e.message)` under a
`result.error.errors` that zod 4 no longer types, and the narrowing that
depended on it). The rule's line shows `+N downstream errors`,
`--details` lists them under their site, and `--json` carries them as
that finding's `downstream`. They are no longer separate breaking sites.
`fix` still verifies against every error.

- [#214](#214)
[`195ae10`](195ae10)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - A rewrite rule
below 80% precision over at least 5 scored ground-truth sites now runs
as assisted in `fix` (react-router `dom-imports`); smaller samples (next
`middleware-proxy`) stay mechanical and are flagged in the compatibility
table. Pack status is unchanged.

- [#218](#218)
[`13e343b`](13e343b)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - pack test runs a
ground-truth repository's code generators (prisma generate, local
graphql-codegen) after its install, as fix does. It fetches the Prisma
engines the locked version needs itself, so generators stay off the
network. A repository whose generator still fails is incomplete and
fails the pack, unless its ground truth declares `withoutGenerated`

- [#227](#227)
[`ae175a9`](ae175a9)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - A migration whose
evidence is larger than GitHub's PR description limit no longer stops
`fix` with "Required migration evidence exceeds the PR body budget" and
loses the run. Long required lists get a compact form when they do not
fit. Lockfile churn shows counts per kind of change and the first
packages. Manual sites are grouped by rule, with counts and the first
locations. Remaining type errors show the first entries. Every full list
stays in report.json and, in the Uptide Action, in the job summary,
which the description links to. If even the compact form does not fit,
the description is a short summary that says so at the top: the verdict,
the checks, the counts and where the full report is. The branch and
report.json are always kept.
  
An agent's decisions under "Decisions for you" name the files the agent
changed, five at most, and count the rest, instead of every file in the
task's scope. A verification-feedback task scoped to the whole
repository made each of its decisions a list of hundreds of files.

- [#219](#219)
[`12dc612`](12dc612)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `fix zod`
rewrites `z.string().uuid()` to `z.guid()` by rule without
`--include-deprecated`: zod 4's `uuid()` also checks the RFC version and
variant, so ids zod 3 accepted stop validating, and `check` now counts
the site as breaking. `fix` no longer reports false pre-existing type
errors on a TypeScript 7 repository whose `lib` names a library newer
than its `target` (`lib: ["ES2023"]` under `ES2022`).

Co-authored-by: uptide-release[bot] <338861287+uptide-release[bot]@users.noreply.github.com>
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.

1 participant