Skip to content

fix(fix): rerun a session's failing test files once before rejecting its edits (UPT-111) - #238

Merged
lucasbraum merged 1 commit into
mainfrom
lucasbraum/upt-111-flaky-test-rerun
Oct 18, 2026
Merged

lucasbraum merged 1 commit into
mainfrom
lucasbraum/upt-111-flaky-test-rerun

Conversation

@lucasbraum

Copy link
Copy Markdown
Collaborator

UPT-111. On dms-saas, the known MongoMemoryServer flake (private-admission.test.ts) failed the end-of-session check in all 4 SDK sessions, so every edit was discarded. The #237 pilot got 0/51 coverage, 10 type errors, and spent $1.02 on a $1 cap.

What changes

  • The session gate reruns failing test files once before it rejects. When a session's tests fail with failures that aren't preexisting, the gate takes the test files the runner named and drops the ones that already failed at the parent or at the session's start. It runs the rest once more, alone (sdk-agent.ts, judge).
    • Every rerun file passes: the files are recorded as llm.tasks[].flaky in report.json and the edits stay.
    • Any file fails again: the session is rejected, as before.
    • A failing run that names no test file (a build step, a timeout), or a rerun that runs nothing: also rejected, as before. No run is no pass.
  • How a file runs alone (TestOptions.only in planTests): with its runner directly, vitest run <files> or jest --runTestsByPath <files>, with the same unit-only isolation as the full run. It never runs a test script, a fallback, a pretest (the first run already built this tree) or the existing unrelated-file retry. A file no runner can take alone (a service test without --with-services, or no runner at all) gets a missing plan, so it can't count as a pass.
  • Final verification keeps its full gate. It still runs the full suite, so a flaky file that fails there fails verification. The PR body's review list gets a line naming the flaky files. It says whether verification's full run passed them or failed them again ("rerun before merging"). A failing verify never turns into a pass.
  • No session the budget can't cover. A verification feedback round is no longer started when what's left of maxCost is below the mean cost of the run's sessions so far. llm.feedback.stopped records the remaining and typical amounts, and the PR's feedback line says so. In feat(fix): fetch the Agent SDK on the first fix, never fall back silently (UPT-108) #237 the fourth session started with $0.09 left after three sessions at about $0.30 each, crossed the cap, and stopped at error_max_budget_usd. It would not start now.
  • Docs: docs/commands/fix.md. Changeset: patch.

Tests

  • sdk-agent.test.ts, session gate:
    • A file that fails once and passes alone keeps the edits and the commit, and is recorded as flaky. Only the new file is rerun, not the parent's own failure.
    • A file that fails twice is rejected and reverted.
    • A rerun that ran nothing is rejected, and so is a failure that names no file.
  • feedback.test.ts, end to end through fix:
    • The flaky session is kept, and report.json and the PR body name the file. If verification's full run fails that file again, verification fails and the body says so; otherwise it passes.
    • Budget stop: at $0.10 a session against a $0.25 cap, the third session is not started, and spend stays at or under the cap. Two existing tests that expected the old overshoot (3 rounds, $0.30 on $0.25) now expect 2 rounds and $0.20.
  • services.test.ts: the exact rerun commands for vitest (unit-only config, and with services) and for jest. A test script that builds first is skipped, and an e2e file is reported as missing.

pnpm turbo run typecheck test for uptide (517) and @uptide/core (2305) passes. biome check . shows only the pre-existing warning and info.

dms-saas rerun

Same harness as #237: #169's bench as the pilot ran it, the packed tarball through plain npx -y file:<tgz>, the Uptide arm, SDK executor, claude-sonnet-5-5, and the $1 cap (--max-cost default). One run. The human reference is round 3's.

executor local checks (install / typecheck / lint / test / build) flaky files sessions cost wall fork CI coverage vs human
this PR sdk ok / ok / ok / ok / ok none 1, accepted; 0 feedback rounds $0.27 234 s 1 pass, 0 fail (fork PR) 10/51, 0 unrelated, no regressions
#237 pilot sdk ok / 10 errors / ok / 1 fail / fail (not detected) 4: 3 rejected on tests, 1 stopped at the budget $1.02 612 s fail at typecheck 0/51
  • The SDK session fixed 10 sites and the rules fixed 2. Uptide's verification passed. Its 3 failing test files (Cannot find package 'vue' from the nested frontend-vue project) are marked preexisting: they fail the same way at the parent.
  • The flake did not fire on this run. The session passed its gate on the first test run, so no rerun happened and nothing was recorded as flaky. This run shows the end result on dms-saas; the rerun path itself is covered by the fixtures above, not by this pilot. Proving it on the real flake needs either more runs or a forced timeout in private-admission.test.ts.
  • The budget stop didn't apply either: one session at $0.27, with no feedback round needed.

🤖 Generated with Claude Code

…its edits (UPT-111)

On dms-saas a known MongoMemoryServer flake (private-admission.test.ts) failed the
end-of-session check in all four SDK sessions, so every edit was discarded.

Before the gate rejects a session on test failures, the new failing test files run
once more, alone, with their runner and the full run's isolation (TestOptions.only).
A file that passes is recorded as flaky (llm.tasks[].flaky, PR review list) and the
edits stay; one that fails twice still rejects; a failure naming no test file is never
rerun. Final verification keeps its full gate and names flaky files that fail there.

Feedback rounds also stop when what is left of maxCost is below the mean cost of the
run's sessions so far (llm.feedback.stopped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
@lucasbraum
lucasbraum merged commit 6f07d3f into main Oct 18, 2026
28 checks passed
@lucasbraum
lucasbraum deleted the lucasbraum/upt-111-flaky-test-rerun branch October 18, 2026 20:02
@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.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