Skip to content

fix: the planner drags nothing through a stationary package's pins, and the agent cannot change dependencies (UPT-126) - #267

Merged
lucasbraum merged 4 commits into
mainfrom
lucasbraum/upt-126-unrelated-major-bump-with-a-false-justification
Oct 20, 2026
Merged

lucasbraum merged 4 commits into
mainfrom
lucasbraum/upt-126-unrelated-major-bump-with-a-false-justification

Conversation

@lucasbraum

@lucasbraum lucasbraum commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

What happened

UpgradeBench v1, guardian/support-dotcom-components, fix typescript 5.9.3 → 6, Uptide 0.18.1. The PR also moved @guardian/ophan-tracker-js 2.8.3 → 3.1.0 and widened src/dotcom's peer range. Its stated reason was "@guardian/libs 33.0.0 pins @guardian/ophan-tracker-js 3.1.0".

The engine did this, not the agent. The bump is in Uptide's mechanical commit (chore: upgrade typescript to 6.0.3 with @guardian/ophan-tracker-js 3.1.0). The agent's commit touches only cdk/tsconfig.json. The reason text comes from check/companions.ts (agreeing()), built from registry manifests; no model output reaches report.companions (fix/run.ts:555 → :1416, fixed before any agent session).

Why the reason was misleading. The pin is real: @guardian/libs@33.0.0 lists @guardian/ophan-tracker-js: 3.1.0 under dependencies. The same manifest also lists it under peerDependencies as ^2.2.10, which 2.8.3 satisfies, and the lockfile already resolved libs' ophan to 2.8.3 through that peer. libs did not move: it stayed at 33.0.0. The planner still applied the pin of a package that wasn't changing.

Changes

1. A package that does not move drags nothing (check/companions.ts, commit 1)

  • A group member decided at its installed version requires nothing new of the others: its pins are what the install already has.
  • A pin that the same manifest also declares as a peer, satisfied by the installed version, moves nothing.
  • A pin reason names the manifest field and the installed version: ai 7.0.9 pins @ai-sdk/provider 4.0.1 in its dependencies; installed 3.0.3.

2. A new major that a third package asks for is a blocker (check/companions.ts, commit 3)

A release-group member that would move to a new major with no migration pack moves only when the upgraded package itself asks for it:

  • the upgraded package's manifest at the target pins the member;
  • the member's manifest declares the upgraded package;
  • the upgraded package's pack names it (companions).

If another moving member asks for it instead, it is listed under Blocked. The blocker names that package, the manifest field and the range: @guardian/libs 33.0.0 declares @guardian/ophan-tracker-js 3.1.0 in dependencies.

Where "asked for by the upgraded package" comes from: the move must come from agreeing() with the lead as host. Every path of agreeing() checks registry manifests:

Path Manifest evidence
Pin The lead's manifest at the target, dependencies[member], exact version
Release pair (react-dom) The member's manifest at the lead's target version declares the lead, in dependencies or peerDependencies, at a range that accepts the target
Peer or range search The member's manifest at the chosen version declares the lead at a range that accepts the target
Shared pin (@ai-sdk/openai) The lead's manifest at the target pins @ai-sdk/provider 4.0.1, and the member's manifest pins the same exact version

The shared-pin path is the one indirect case: neither package declares the other. Two non-manifest paths are also exempt:

  • @types/* follows the package it types: the @types/<pkg> name and the DefinitelyTyped major (check/types-release.ts). This is not manifest data; say if it should be refused too.
  • The pack's releaseGroup is pack metadata.

react-dom with react, @ai-sdk/* with ai and @sentry/profiling-node with @sentry/node still move; their tests now also assert that nothing is blocked.

3. The agent cannot change dependencies (fix/dependency-edits.ts, guard and editRules in fix/sdk-agent.ts, commits 2 and 4)

The agent could edit a package.json whenever it was a task file (the sentry pack reports sites on package.json script lines), and editRules only checked which files changed. It could also Write a new package.json, lockfile or .pnpmfile.cjs.

  • editRules diffs every changed package.json against the commit before the session. These are refused:

    • a version or range change, or a removal, of any existing dependency in dependencies, devDependencies, peerDependencies or optionalDependencies, including the upgraded package and its planned companions (the engine wrote those);
    • any edit to overrides, resolutions or pnpm.

    A rewrite to the same value is allowed. Engine-written pins and overrides, including UPT-125's peer overrides, are committed before the session, so they are never flagged.

  • A refused task resets, as with the test-edit gate. The refusal names each package and goes into the next feedback round's prompt, including a refusal from the first pass. The existing 3-round limit bounds an agent that keeps retrying.

  • Refused edits are recorded in report.json as llm.tasks[].outOfScopeDependencyEdits (file, field, name, before, after, reverted: true).

  • A new dependency is kept. It is recorded as agentAddedDependencies and listed in the PR body under Added by the agent. It never blocks publication.

  • guard refuses lockfiles (npm, pnpm, Yarn classic and Berry, Bun), .npmrc, .yarnrc, .yarnrc.yml, .pnpmfile.cjs, pnpm-workspace.yaml and new package.json files, before the edit happens.

Tests

  • Guardian, every layer:
    • libs staying (with the peer, and with the pin alone): no companion;
    • libs moving: a blocker naming libs, dependencies and 3.1.0; the same plan with ophan named by the lead's pack moves it;
    • the agent's bump and the nested peer widening: refused, reverted and recorded, whatever reason the agent gives.
  • Release pairs still move: react → react-dom, ai → @ai-sdk/*, @sentry/node → @sentry/profiling-node, with no blocker.
  • Upgraded package's range:
    • changed by the agent → refused;
    • rewritten identically → allowed.
  • Added dependency: kept, recorded and listed. Removals and override edits are refused. Engine overrides, kept or reformatted, are not flagged.
  • End to end through fix():
    • an agent that re-bumps every round is refused in all 3 rounds, and rounds 2 and 3 are told why;
    • a refusal in the first pass reaches round 1.
  • Package-manager files: guard and editRules refuse each one.
  • Mutation checks: each new branch was disabled in turn and its test failed: 4 for commit 1, 3 for the blocker rule, 11 for the gate, 2 for the corrected exemption.

Local, on main with UPT-125: core 2453 passed, CLI 538 passed, typecheck 3/3. Lint shows the same 2 warnings and 1 info as on main.

Known limitations

  • Additions in feedback rounds (UPT-129): in verification feedback rounds no package.json is a task file, so an agent's new dependency is refused there. Additions are possible only in the first pass, when a manifest holds a site.
  • run_tests (follow-up ticket): it runs repository test files related to the task, including ones the agent creates. A test file can run arbitrary code, so it could run the package manager.
  • Naming a blocked companion on the command line: fix typescript @guardian/ophan-tracker-js does not lift the blocker yet. peer-preflight.ts plans without the command-line names and rejects a named package that is not a peer blocker. It was left untouched, as asked. The blocker's next step says to upgrade the package on its own first.

🤖 Generated with Claude Code

lucasbraum and others added 2 commits October 20, 2026 01:40
…ns (UPT-126)

`fix typescript` on guardian/support-dotcom-components moved @guardian/ophan-tracker-js
2.8.3 -> 3.1.0 and widened src/dotcom's peer range. @guardian/libs 33.0.0 stays where it
is, yet the planner moved what it pins. The same manifest also declares the package as a
peer (^2.2.10) that 2.8.3 satisfies, and the lockfile already resolves that peer.

- A member decided at its installed version requires nothing new of the others.
- A pin the same manifest also declares as a satisfied peer moves nothing.
- A pin reason names the manifest field and the installed version.

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

The agent session could edit any package.json that was one of its task files (the
sentry pack reports sites on package.json script lines), and editRules checked only
which files changed. It could also create a new package.json, lockfile or .pnpmfile.cjs
anywhere.

- editRules diffs every changed package.json against HEAD. Changing or removing an existing
  dependency, or any edit to overrides, resolutions or pnpm, is refused unless the
  dependency is the upgraded package or a planned companion. The task resets, as with the
  test-edit gate.
- A new dependency is kept, recorded as agentAddedDependencies and listed under "Added by
  the agent".
- Refused edits are recorded as outOfScopeDependencyEdits. The refusal reaches the next
  feedback round, including one refused in the first pass. The existing 3-round limit bounds
  repeats.
- guard refuses lockfiles, .npmrc, .yarnrc(.yml), .pnpmfile.cjs, pnpm-workspace.yaml and new
  package.json files, before the edit happens.

Known gap: run_tests executes repository test files, including ones the agent writes, which
could run the package manager. That is a follow-up ticket.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
@tenki-reviewer

tenki-reviewer Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Review complete. No blocking issues — approved ✅; 2 nitpicks below.

🧹 Nitpicks (2) — 🟢 2 low
  • 🟢 Unescaped agent dependency fields in PR body (report.ts:1291) — The new "Added by the agent" section interpolates agent-controlled strings raw into PR markdown: d.after, d.file, and d.field are unquoted and a backtick inside d.name terminates the code span (packages/core/src/fix/report.ts:1291).
  • 🟢 Package-manager gate misses Yarn PnP loader files (dependency-edits.ts:16) — packageManagerFile covers lockfiles, .npmrc/.yarnrc*, .pnpmfile.cjs and pnpm-workspace.yaml, but not Yarn Berry's .pnp.cjs/.pnp.js loaders or .yarn/ state such as .yarn/config.yml and .yarn/plugins/*.cjs (packages/core/src/fix/dependency-edits.ts:16).

This PR teaches the autonomous fix agent to propose dependency edits safely: package-manager manifests are gated, refused edits are reverted, and accepted edits are summarized for reviewers. Feedback rounds now seed the agent's prompt with the previous attempt when a task was both edited and rejected. Companion pinning gets a short-circuit when a same-manifest peer already satisfies the requested pin.

Files Change
packages/core/src/fix/dependency-edits.ts (+tests) New gate identifying package-manager files and classifying dependency edits as in/out of scope.
packages/core/src/fix/sdk-agent.ts Wires the gate into editRules, reverting refused writes and recording them for reporting.
packages/core/src/fix/run.ts, fix/feedback.test.ts Adds feedback rounds: previousAttempt propagation and refusal handling across rounds.
packages/core/src/fix/report.ts, fix/types.ts New "Added by the agent" section listing accepted dependency edits in the PR body.
packages/core/src/check/companions.ts (+tests) Suppresses EXACT pins when the same manifest declares a satisfied peer dependency.
.changeset/* Changesets for the dependency-edit gate and companion-pin behavior.

Reviewed commit: 4a7f201

lucasbraum and others added 2 commits October 20, 2026 08:08
…companion (UPT-126)

A release-group member that needs a new major moves only when the upgraded package itself
asks for it, from manifests or pack metadata. Either the lead's manifest at the target
pins it, or its own manifest declares the lead (agreeing() with the lead as host), or the
lead's pack names it (`companions`). When another moving member's manifest asks for it,
it becomes a blocker. The blocker names that package, the field and the range, e.g.
"@guardian/libs 33.0.0 declares @guardian/ophan-tracker-js 3.1.0 in dependencies".

react-dom with react, @ai-sdk/* with ai and @sentry/profiling-node with @sentry/node still
move; their tests now also assert that nothing is blocked.

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

The engine writes the upgraded package's and its planned companions' versions before the
agent session starts, so the gate no longer exempts them. Any change to their version or
range is refused like any other dependency edit. A rewrite to the same value is still
allowed. Removes the scope plumbing that existed only for the exemption.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
@lucasbraum
lucasbraum merged commit 907dffb into main Oct 20, 2026
28 checks passed
@lucasbraum
lucasbraum deleted the lucasbraum/upt-126-unrelated-major-bump-with-a-false-justification branch October 20, 2026 16:33
@uptide-release uptide-release Bot mentioned this pull request Oct 20, 2026
lucasbraum pushed a commit that referenced this pull request Oct 20, 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.18.2

### Patch Changes

- [#267](#267)
[`907dffb`](907dffb)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - The agent can no
longer change dependencies outside the upgrade. An agent edit that
changes or removes an existing dependency in any `package.json`, or
touches `overrides`, `resolutions` or the `pnpm` field, is refused and
reverted. Uptide sends the refusal back to the agent in the next
feedback round, naming the package, and records it in `report.json`
(`outOfScopeDependencyEdits`). That includes the upgraded package and
its planned companions, whose versions the engine wrote: only a rewrite
to the same value is allowed. The agent may still add a new dependency:
it is kept, recorded (`agentAddedDependencies`) and listed under "Added
by the agent" in the PR body. Lockfiles, `.npmrc`, `.yarnrc(.yml)`,
`.pnpmfile.cjs`, `pnpm-workspace.yaml` and new `package.json` files are
read-only for the agent.

- [#267](#267)
[`907dffb`](907dffb)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - A companion that
would move a new major is planned only when the upgraded package asks
for it: its own manifest at the target pins the companion, the
companion's manifest declares the upgraded package, or the upgraded
package's pack names it. When a third package asks for it
(`@guardian/libs` 33 pinning `@guardian/ophan-tracker-js` 3.1.0 during a
typescript upgrade), Uptide no longer moves it. The PR lists it under
"Blocked" with the declaring package, the manifest field and the range.
Release pairs still move with their lead: `react-dom` with `react`,
`@ai-sdk/*` with `ai`, `@sentry/profiling-node` with `@sentry/node`.

- [#267](#267)
[`907dffb`](907dffb)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - A package that
does not move no longer drags another one through its pins: `fix
typescript` on guardian/support-dotcom-components moved
`@guardian/ophan-tracker-js` 2.8.3 → 3.1.0 because `@guardian/libs`
33.0.0, which stayed, pins it. A pin the same manifest also declares as
a peer the install satisfies moves nothing either, and a pin reason now
names the manifest field and the installed version.

- [#266](#266)
[`a5e3f57`](a5e3f57)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `uptide fix` no
longer upgrades a peer blocker across a major that no migration pack
covers. Before, `redis` 5 → 6 next to `typeorm` 0.3 (which peers on
redis ^5) moved typeorm to 1.1.0 and ended in an agent-led draft. Now
the blocker keeps its version, with a peer override scoped to it in
package.json: npm `overrides.<blocker>.<peer>`, pnpm
`pnpm.peerDependencyRules.allowedVersions`, Yarn
`resolutions["<blocker>/<peer>"]`. The lockfile scope check still covers
the install. The PR body names the override, which release was not taken
and why. In `report.json`, each `peerConflicts` entry now has `strategy`
(`override`, `blocked` or `none`), `declined` and `override`. If
verification fails with the override, the blocker is reported as a
blocker and is still not upgraded. A blocker you name on the command
line, or one whose major a pack covers, is upgraded as before.

- [#264](#264)
[`2089d0a`](2089d0a)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `uptide fix` no
longer stops when the package manager resolves a different version than
the target. Yarn classic ignored the pin for a direct dependency and
resolved a range such as `^7.0.105` to its newest match (7.0.146). Now
the lockfile resolves exactly the target with npm, pnpm, Yarn classic
and Yarn Berry, and the manifest keeps its range. If the package manager
still resolves another version, the run continues only when that version
is in the same major, inside the manifest range and covered by the pack.
The PR body, the terminal summary and `report.json` (`resolvedVersion`,
`lockfile.resolved`) then name both versions. Otherwise the run stops
with an error that names the target, the resolved version and the
reason.

- [#268](#268)
[`4cee03a`](4cee03a)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `fix` computes
the agent's cost itself, per model and token class, from Uptide's price
table: `report.json` and `--json` list each model's uncached input,
cache read, 5-minute and 1-hour cache write and output tokens, the rate
applied to each and the cost, so the total can be redone by hand. The
Agent SDK's own figure is kept apart as `sdkReportedCostUsd`;
`--max-cost` counts the higher of the two, a gap above 5% is printed as
a warning, and a model missing from the table uses the SDK's figure,
marked unverified. `llm.inputTokens` now counts uncached input only.

- [#268](#268)
[`4cee03a`](4cee03a)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - Sonnet 5.5 cache
reads are priced at the published $0.10 per million tokens (0.05x
input), not $0.20. Every Anthropic rate in the price table was checked
against Anthropic's pricing page on 2026-10-20, and a test now pins each
one.

Co-authored-by: uptide-release[bot] <338861287+uptide-release[bot]@users.noreply.github.com>
lucasbraum added a commit that referenced this pull request Oct 22, 2026
…o the upgrade's copy (UPT-134, UPT-135) (#271)

Hotfix for 0.18.3. Three signed-off commits, in order: UPT-134, UPT-135,
and the small group-title fix.

## UPT-134: the agent silently does nothing

**Root cause** (`packages/core/src/fix/sdk-agent.ts`, `sdkRunner`, lines
1102–1117 at c64fb88). When the Anthropic API refuses the request
(invalid key, empty credit balance, a model the key cannot use), Agent
SDK 0.3.296 emits one `result` with `subtype: "success"`, `is_error:
true`, `num_turns: 1`, `total_cost_usd: 0` and the API's message in
`result`, then throws `Claude Code returned an error result: …`. The
runner's `catch` kept the result and dropped the throw (`if (!result)
throw err`), and the return only looked at `subtype !== 'success'`. So
the task came back as a clean session with no summary: gate `no-change`,
every site "SDK agent made no change", and up to 3 feedback rounds that
hit the same error. That is the reported signature: `agent` a few
seconds, `feedback` a few seconds, `$0.00`.

Measured directly against SDK 0.3.296 with a bad key and with a model
the key cannot use: both return
`{"subtype":"success","is_error":true,"num_turns":1,"total_cost_usd":0}`
in about 1.2 s, then throw.

**Not a 0.18.2 regression.** Both lines date from 405f429 (2026-10-12).
0.18.1 from npm reproduces the same thing (below). PRs #267 and #268 are
not involved.

**UpgradeBench riseup-oss/mcp (jest 29 → 30) is a different case.**
Uptide found 0 sites, and types, tests and build pass after the bump, so
no agent task was planned (`tasks: []`). The ground truth agrees: the
bump alone equals the human commit. That $0 was legitimate; the run just
never said why. It does now.

**Fix**
- `sdkRunner` keeps the API's message (`sessionError`). A `stop_reason:
refusal` is recorded as `stop: 'refusal'`.
- That task's gate is `error`, and whatever the session wrote before the
error is reset. Feedback starts no further session after an error.
- Safety net, whatever the cause: every task that keeps nothing records
why in `llm.tasks[].noChange`. The reasons are the API's error, no turn,
no output tokens, a refusal, a stop, every edit rejected (with the
gate's reason), or the agent's own `couldNotDo`.
- When nothing the agent did stayed, `llm.agentIdle` carries the reason.
The terminal and the PR body print "The agent did not change anything:
<reason>". The PR body no longer says "The code was migrated" when every
site is still manual.

## UPT-135: pnpm lock guard false positive

I could not get pnpm 10.34 to produce the reported resolutions on a
fresh fixture, so the shape is reproduced at the guard with synthetic
lockfiles (`fixtures/lockfiles/onto-upgrade`, pnpm, npm and Yarn Berry).
The "same version, different resolution" entries were not peer suffixes.
pnpm moved unchanged packages' dependencies (`qs`, `iconv-lite`) onto
the newer copies express 5 brings. The copies they left were outside the
upgrade's subtree, so each one read as "→ none", and every package
depending on them read as "X → X (same version, different resolution)".
On c64fb88 the fixture gives exactly that list. A run like this falls
back to an agent-led draft.

**Fix** (`packages/core/src/fix/managers/lock-guard.ts`, shared by all
three managers)
- An edge of an unmoved package may move onto the upgrade's copy when it
stays in the same major (the same minor below 1.0) and is strictly
newer. Importers' own declarations still move only through the plan.
- A copy that left is in scope when everything that reached it is: the
subtree, an accepted dependent, or another such copy.
- Each move is listed as housekeeping: `housekeeping.onto` in
report.json, a line under **Lockfile housekeeping** in the PR body, and
a count in the terminal.
- **npm** had the same false positive (a nested copy dropped for the
hoisted one) and is fixed by the same code.
- **Yarn Berry** had it too, in a different form: the descriptor
`qs@npm:^6.10.0` changes 6.12.0 → 6.16.1 in place. Yarn keys descriptors
apart from the upgrade's own `qs@npm:^6.14.0`, so the upgrade's copy is
matched by content there, and an in-place descriptor move is accepted
under the same rule. npm placements are still matched by key only: a
nested npm copy that changes in place is another copy on disk, and stays
refused (existing `npm-lock.test.ts` case).
- Still refused: a move to a release the upgrade did not bring (the
control), a downgrade, a move across a major, a 0.x minor, and anything
the repository declares itself.

## Small: group titles

The PR body headed each group of sites with its key. That was the rule
id (`params-string-array`) or, for sites no pack rule covers, the
changed declaration's path. `e.query` is how @types/express names
`express.query`. The run now records each site rule's summary
(`ruleSummaries`), and groups are titled by it. A declaration path gets
the compiler's title at its sites, or "`e.query` was removed in express
5.3.0". Stored reports without summaries keep the id.

## Evidence

Case A: riseup-oss/mcp at `0d0e55a` (bench secondary base), read-only
clone, remotes removed. Case B: a local express 4.21.2 → 5.3.0 fixture
on pnpm, with 6 `req.params`-as-string sites and 1 `express.query()`
site (all manual: the express pack has no rewrites), plus
`@bull-board/express` 5.23.0, which keeps express 4 as its own
dependency. Every run used `--max-cost 0.25`, with the key loaded per
command.

| Run | Uptide | Key | Result | `llm` |
|---|---|---|---|---|
| A before | 0.18.2 npm | real | 0 sites, nothing said | tasks `[]`, $0,
0/0 tokens |
| A before | 0.18.1 npm | real | same | tasks `[]`, $0 |
| A after | this branch | real | PR body: "The agent did not change
anything: nothing was left for it: no site needed it, and the
type-check, tests and build had no new failure to repair." | tasks `[]`,
$0 |
| B before | 0.18.2 npm | rejected by the API | 7 manual, "SDK agent
made no change", 4 sessions (site task + 3 feedback rounds) | each:
turns 1, 0/0 tokens, $0, gate `no-change`, no stop, no error |
| B before | 0.18.1 npm | rejected by the API | identical | identical |
| B after | this branch | rejected by the API | 7 manual; terminal and
PR body: "The agent did not change anything: the session failed: Invalid
API key · Fix external API key"; 1 session, no feedback round | turns 1,
0/0 tokens, $0, gate `error`, `error` and `noChange` set |
| B | 0.18.2 npm | real | 7/7 fixed by the agent | turns 14, 2,354
output tokens, $0.0605, gate `accepted` |
| B after | this branch | real | 7/7 fixed by the agent, no `agentIdle`
| turns 14, 2,508 output tokens, $0.0636, 22.0 s, gate `accepted` |

With a working key, case B runs fine on 0.18.2. Its before-state ($0, 0
fixed) needs the API to refuse the request, which is the failure this
fixes. Case B on pnpm 10.34 wrote no out-of-scope entries on either
version; the reported out-of-scope list is reproduced at the guard
(above).

Spend: $0.12 in total ($0.0605 + $0.0636); every other run and probe was
$0.

## Checks (run locally the way CI runs them, API keys unset)

```text
pnpm install --frozen-lockfile --ignore-scripts   ok
pnpm lint                                         Checked 952 files. No fixes applied. 2 warnings, 1 info (all in files this PR does not touch)
node scripts/private-material.mjs                 1513 tracked files, 1 private identifiers: clean   (EVENT=pull_request, dummy UPTIDE_PRIVATE_DENYLIST)
pnpm build                                        Tasks: 2 successful, 2 total
pnpm typecheck                                    Tasks: 3 successful, 3 total
pnpm test  @uptide/core                           Test Files 203 passed | 4 skipped (207)   Tests 2487 passed | 37 skipped (2524)
pnpm test  uptide (cli)                           Test Files 48 passed (48)   Tests 539 passed (539)
pnpm docs:packs --check / pnpm docs:links         ok; 84 Markdown files, every relative link resolves
```

Under `turbo run test` on my machine, 6 core tests (npmrc, list) and 5
CLI tests (failures, smoke) fail on this branch and on a clean c64fb88
alike. Turbo gives the tests `TMPDIR=/private/tmp`, and a stray
`/private/tmp/package.json` left by an earlier bench run makes `/tmp`
look like a project root. The totals above are each package's suite run
directly. CI's runner has neither.

Mutation checks: 12/12 mutations killed for UPT-134, 14/14 for UPT-135
(after two tests were added for the two that first survived), and 5/5
for the titles.

## Open questions

- The tester's actual API error is unknown. With this build their run
will print it. Worth asking them to re-run, or to send `llm.tasks` from
the 0.18.2 report.json if they still have it.
- UPT-135 accepts a dependency moving forward onto the upgrade's copy
within a major. That is a real resolution change for an unchanged
package (another package's `qs` moves forward), only listed, not
refused. Is that the line you want, or should it stay refused outside
agent-led runs?
- `uptide fix` on a checkout without `node_modules` fails with "express
has no upgrade to 5.3.0" (`fix/run.ts:496`): the check finds no
installed version. That is separate from this PR and is not fixed here.

🤖 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>
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