Skip to content

fix: the PR's build line states the build's result, never "not required" in its place (UPT-110) - #233

Merged
lucasbraum merged 1 commit into
mainfrom
lucasbraum/upt-110-build-line
Oct 18, 2026
Merged

lucasbraum merged 1 commit into
mainfrom
lucasbraum/upt-110-build-line

Conversation

@lucasbraum

Copy link
Copy Markdown
Collaborator

Closes UPT-110.

What and why

In the 0.15.1 pilot (#169), enki's PR said "build not required (pack-verified migration)", but npm run build fails on the final head. A pack without requiresBuild never ran the repository's build, and the report filled that gap with "not required". That reads like a pass.

Change

  • fix runs the repository's build once on the final head when the pack doesn't gate on it (run.ts). This happens after the feedback rounds, in a new build phase so the phases still add up to the run's wall time. Its results join verification.build.

  • verify rebuilds on its own head (reverify.ts). Before, it reused the stored build result for packs that don't require one, so the result could be stale.

  • The build line (verifiedBy, report.ts) now shows one of these:

    • ✅ build
    • ❌ build: <first error line> (escaped for the table, max 160 characters), plus the existing verify.build hint
    • ⚠️ build not run, or ⚠️ build not run: no build script

    When a pack exists but doesn't require the build, "(not required by the pack)" or "(not required by this candidate pack)" goes after the result. Generic runs, pin mode and packs that require a build get no note.

Decisions to flag

  1. A build the pack doesn't require is reported but doesn't block. verification.passed, buildFallback and publicationBlockers work as before. So a PR can be ready for review while the line says ❌ build … (not required by the pack). If a build that fails on the head (and passed at baseline) should block, that's a follow-up. It would also need a baseline build to tell "already failing" apart from "newly broken", and that costs a second build per run.
  2. This costs one more build per pack-verified run. In the consumed-workspace case (UPT-104), packages/internal is now built twice on the final head: once by the consumed rebuild and once by the head build. I didn't dedupe this.

Tests

  • New fixture in required-build.test.ts: a pack with requiresBuild false and a failing repo build renders ❌ build: src/db.ts(3,8): error TS2307: … (not required by the pack). It also checks the body has no "build not required" and the build is not a blocker. Without the fix it fails: the build is never called.
  • It replaces "leaves packs without requiresBuild unchanged", which asserted the old behavior.
  • Updated build-call counts and rows in three UPT-104 tests in fix.test.ts, and the wording in report and report-budget snapshots.
  • Lint clean on the changed files. On main, biome check already reports 1 warning and 1 info. Typecheck passes. After pnpm build: core 2293 passed, cli 516 passed. No bench runs.

🤖 Generated with Claude Code

…ed" in its place (UPT-110)

A pack without requiresBuild never ran the repository's build, and the
Verified by row read "build not required (pack-verified migration)" even
where `npm run build` fails on the final head (enki, #169).

fix now runs the repository's build once on the final head when the pack
does not gate on it, in its own `build` phase, and verify rebuilds on its
head instead of keeping a stale stored result. The build line names one
of: passed, failed with its first error, or not run and why. "Not
required by the pack" sits beside that result. The pack's gate is
unchanged: a build it does not require is reported, not a blocker.

Closes UPT-110.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
@lucasbraum
lucasbraum merged commit 1ad8071 into main Oct 18, 2026
28 checks passed
@lucasbraum
lucasbraum deleted the lucasbraum/upt-110-build-line branch October 18, 2026 18:12
@uptide-release uptide-release Bot mentioned this pull request Oct 18, 2026
lucasbraum added a commit that referenced this pull request Oct 18, 2026
…o (UPT-110) (#236)

Follow-up to #233 (UPT-110).

## What and why

#233 runs the repository's build on the final head when the pack doesn't
gate on it, and reports the result without blocking. So a PR could open
as ready for review with `❌ build …` in its checks. This PR doesn't add
a baseline build to every run. Instead, a failing head build is compared
with the parent commit.

## Change

- **Parent build, only when the final-head build fails** (`run.ts`,
`buildAtParent`). It runs in the same clone: `git switch --detach
<baseHead>`, frozen install, generated clients, build. Then it resets,
switches back to the branch, and reinstalls and regenerates.
- **Parent fails too:** not blocking. The build line reads `❌ build:
<first error> — already failing before this upgrade (not required by the
pack)`.
- **Parent passes:** the upgrade broke the build. `verification.passed`
is false and the verdict reads `verification failed: build broken by
this upgrade`. The PR body opens with `> ❌ **Build broken by this
upgrade.** …` (in the shortened body too). The build line ends `— broken
by this upgrade`.
- **Draft, not refused.** `verification.buildBroken` is set only when
the broken build is the run's sole failure. In that case
`publicationBlockers` lets it through, and `publish` forces `--draft`
(and `gh pr ready --undo` on an open PR), the same way it handles
agent-led drafts. If other checks also fail, the run is refused as
before.
- **`verify`** (`reverify.ts`) keeps the stored `parentBuild` while its
own head build still fails (the parent commit hasn't changed), and drops
it once the head builds.
- **No duplicate head build.** Before the head build, the consumed
builds are brought up to date (no-op when the tree is unchanged). Any
workspace they already built on this head is skipped. The verification
build case is unchanged: it already skips the head build entirely.

## Decisions to flag

1. **The UPT-104 build counts are one above pre-#233, not equal to
them** (agreed in review: 4 / 3 stays). The duplicate
(`packages/internal` built twice on the head) is gone. The root is still
built once, because no other build covers it on the head, and that's the
build this check is for. Counts: judged-on-head 3 → 5 (#233) → **4**;
nothing-changed 2 calls / 2 runs → 3 / 4 (#233) → **3 / 3**. Getting
back to exactly 3 / 2 would mean not building the root at all.
2. **A parent that can't be checked out, installed or built** is
recorded as `verification.parentUnchecked` (for example `install failed:
<first line>`). The run still fails and publishes only as a draft. The
PR opens with `> ❌ **Build fails on this upgrade; the parent build could
not be checked (<reason>).**`, the build line ends `— the parent build
could not be checked`, and the verdict says the same. Nothing claims the
parent passed.
3. **The clone's build output is the parent's after the comparison.**
Nothing later in `fix` reads it, and `verify` builds again. Marked
`ponytail:`.
4. **The GitHub Action** publishes agent-led runs whose verification
passed (every Action PR is a draft through `uptide pr`). A run whose
only failure is the broken build now opens the same way: its gate is
`passed || buildBroken`, so the PR is a draft with the banner at the
top. Its run-report row stays `failed` with `verification failed: build
broken by this upgrade` (or `the build fails and the parent build could
not be checked`) and links the PR.
5. **Not covered:** `verify` doesn't build the parent itself
(`ReverifyServices` has no install). If a head passed in `fix` and fails
in `verify`, it gets the #233 behavior: reported, not blocking. Runs
that already gate on the build (generic, `requiresBuild`) are unchanged:
their failing build still refuses publication.

## Tests

- `required-build.test.ts`: two fixtures. The build mock fails on the
branch and tells the parent apart by detached HEAD.
- Parent also fails: built on the branch then the parent, two frozen
installs, back on the branch at the verified head, `— already failing
before this upgrade` in the line, passes, no blockers.
- Parent passes: fails, `buildBroken`, verdict text, body opens with the
banner, no blockers (refused without `buildBroken`). `verify` keeps it
while the head fails and drops it once the head passes.
- `publish.test.ts`: a `buildBroken` run publishes with `--draft` even
when `draft: false` is passed.
- `required-build.test.ts`, third fixture: the parent's install throws.
The run never builds at the parent, comes back on the branch, fails with
`parentUnchecked: install failed: …`, the body opens with the "could not
be checked" banner and never says "passed at the parent", and it
publishes as a draft.
- `action/run.test.ts`: a build-broken run is published by the Action,
with a `failed` row that links the PR and names the reason.
- `fix.test.ts`: the UPT-104 tests and "records baseline builds…" assert
the head build of `['.']` only, with the counts above.
- `pnpm lint` (only the warning and info already on main), `pnpm
typecheck`, `pnpm build`, then core 2300 passed / 32 skipped, cli 517
passed (second commit). No bench runs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.16.0

### Minor Changes

- [#237](#237)
[`717468a`](717468a)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `npx uptide fix`
gets the Agent SDK executor. The package stays as small as before:
`@anthropic-ai/claude-agent-sdk` is not installed with Uptide. An SDK
installed next to Uptide is used as it is; otherwise the first `fix`
that needs it installs the version Uptide is tested with into Uptide's
cache (`~/.cache/uptide/agent-sdk/<version>`), says so in one line with
its size, and later runs reuse it. When that install fails (offline, a
proxy) or the SDK cannot run, `fix` runs the patch executor and says so
on the first line of its output and at the top of the PR body, with the
reason and how to get the SDK executor. `report.json` records
`llm.executor` and, after a fallback, the reason in `llm.sdkMissing`.
The privacy notice names the SDK only when the SDK will run, and the LLM
line names the executor. `--agent sdk` without a usable SDK stops before
any work instead of failing mid-run.

- [#239](#239)
[`e1e8c40`](e1e8c40)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `uptide fix` runs
only with the Claude Agent SDK; if it can't, it doesn't run. Before the
first download, an interactive terminal asks once: `uptide fix needs the
Claude Agent SDK (about 300 MB, downloaded once to <cache>). Download
now? [Y/n]` (default yes). With `--yes`, or without a terminal, it
downloads and says so in one line. Once cached, it never asks again.
Answering `n`, an SDK that can't be fetched or loaded (offline, a proxy,
no binary for the platform), a missing `ANTHROPIC_API_KEY`, another
provider, or `llm.enabled: false` / `executor: patch` in `uptide.yml`
all stop `fix` with exit 2 before any clone or spending. The run prints
one message with the reason and the fix, and `--json` returns it as
`error.code: "SDK_UNAVAILABLE"`. There is no fallback to the patch
executor anywhere: the Uptide Action stops the run with the same
message. `--no-llm`, `--agent` and `--provider` are gone from help and
docs. `llm.sdkMissing` is gone from `report.json`, and `uptide init` no
longer writes `executor`.

### Patch Changes

- [#236](#236)
[`9b35b9d`](9b35b9d)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - A repository
build that fails on the final head is no longer published as a ready PR.
When it fails, `fix` builds the parent commit in the same clone to
compare. If the parent fails too, the build line says "already failing
before this upgrade" and nothing is blocked. If the parent passes, the
upgrade broke the build: verification fails, and the PR opens only as a
draft with "Build broken by this upgrade" at the top. If the parent
can't be checked out, installed or built, verification still fails and
the PR says "Build fails on this upgrade; the parent build could not be
checked" with the reason, never that the parent passed. The GitHub
Action opens these runs as draft PRs, the same way it opens agent-led
runs. The head build also skips workspaces already built on that head,
so a workspace whose output a consumer reads is no longer built twice.

- [#233](#233)
[`1ad8071`](1ad8071)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - The PR
description's build check now gives the build's actual result: passed,
failed with its first error, or not run and why. When a migration pack
doesn't require a build, `fix` still runs the repository's build once on
the final head, and `uptide verify` runs it again, and reports what
happened. "Not required by the pack" now appears next to that result
instead of replacing it. Before, the line read "build not required" even
when the repository's build failed. A build the pack doesn't require
still doesn't block verification or publishing.

- [#238](#238)
[`6f07d3f`](6f07d3f)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - An SDK session's
edits are no longer thrown away because of one flaky test. Before the
gate rejects a session on test failures, it runs the failing test files
once more, alone, with their runner and the same isolation as the full
run. A file that passes then is recorded as flaky (`llm.tasks[].flaky`
in `report.json`, and a line in the PR body's review list) and the edits
stay. A file that fails twice still rejects them, and a failure that
names no test file (a build, a timeout) is never rerun. Final
verification keeps its full gate: it runs the flaky files again, and if
one fails there, verification fails and the PR body says so. `fix` also
stops starting verification feedback sessions when what is left of
`--max-cost` is below the mean cost of the run's sessions so far, so it
no longer starts a session it can't afford; the PR's feedback line says
when this happened.

- [#234](#234)
[`066deab`](066deab)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `fix` on npm no
longer loses a package the repo imports without declaring it when the
ERESOLVE retry re-resolves the lockfile. If the retry drops such a
package from the top of `node_modules`, it runs again keeping that
package and the locked packages that require it at their old versions;
if that conflicts, the first retry stands. Either way the summary and
the PR body name the package under "Imported but not declared" and
suggest adding it to package.json. `fix` never adds it for you.

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