Repository navigation
fix(install): resolve the exact target on Yarn classic and accept a same-major patch it cannot pin (UPT-124) - #264
Merged
lucasbraum merged 2 commits intoOct 20, 2026
Conversation
|
Review complete. No issues found — approved ✅. The change threads a
Verification found no issues at or above the reporting threshold; a few medium/low-severity edge cases (fallback gating skipping the pack-coverage check, CRLF lockfiles defeating classic pinning) remain as informational observations. Reviewed commit: 31d3971 |
…ame-major patch it cannot pin (UPT-124) Yarn classic applies root `resolutions` to transitive dependencies only, so a direct `ai@^7.0.105` resolved its newest match (7.0.146) and `uptide fix` stopped with exit 2 before any LLM call. The range's lockfile pattern now moves onto the entry at the target. When a manager still resolves another version, the run continues only if it is the same major, inside the manifest range and covered by the pack; both versions are reported (`resolvedVersion`, `lockfile.resolved`, PR body, terminal). Anything else stops with the target, the resolved version and the reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
… when agent-led (UPT-124) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
lucasbraum
force-pushed
the
lucasbraum/upt-124-target-version-is-not-what-the-package-manager-resolves-yarn
branch
from
October 20, 2026 07:05
31d3971 to
ac59ee1
Compare
lucasbraum
deleted the
lucasbraum/upt-124-target-version-is-not-what-the-package-manager-resolves-yarn
branch
October 20, 2026 07:16
Merged
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes UPT-124.
Root cause
The fatal check is
packages/core/src/fix/managers/upgrade.ts:479on main:Requested …, but … resolved …; verification cannot use a different target.It also has a second copy after the install,assertInstalledVersionsinfix/versions.ts:56.The check was right; the pin before it failed on Yarn classic.
installPinnedpins Yarn through rootresolutions, but Yarn classic appliesresolutionsto transitive dependencies only. A direct"ai": "^7.0.105"therefore resolves its newest match. I reproduced this by hand on stakwork/strut:resolutions: { ai: "7.0.105" }the lockfile getsai@7.0.105 → 7.0.105andai@^7.0.105 → 7.0.146;7.0.105and then restoring^7.0.105resolves 7.0.146 again;ai@^7.0.105to theai@7.0.105entry's header holds throughyarn installandyarn install --frozen-lockfile.Berry already pins through
yarn set resolution. npm and pnpm pin the manifest and then reuse the satisfying entry. The network-gated matrix incompatible-ranges.test.tscovers npm, pnpm and Berry 3/4, but had no Yarn classic case.What changed
pinClassicPatternsmoves each range's pattern (ai@^7.0.105) onto the lockfile entry at the target and drops the entry that is left empty. It runs before the reconcile, in the same place Berry runsset resolution. The manifest keeps its range (^7.0.105stays caret). TheLOCKFILE_OUT_OF_SCOPEguard still runs on the final lockfile, unchanged.upgradeInstall): if the lockfile still resolves another version, the run continues only if that version is in the target's major, satisfies the manifest range (npm aliases unwrapped), and is the same in every workspace. Inrun.ts,pack.supports(from, resolved)must also hold. Anything else stops withRequested <name>@<target>, but <workspace> resolved <version>: <reason>. The reason is one of:a different major than …,outside the manifest range …,another workspace resolved …,nothing is installed, oroutside what the <pack> pack covers.lockfile.resolved: [{ name, requested, version }]on the install report, andresolvedVersionnext totargetinreport.json(FixResult, typed infix/types.ts). The PR body and the terminal summary get a Resolved version differs from the target section. BothassertInstalledVersionscalls inrun.ts, and the one inreverify.ts, use the resolved versions. When target and resolved already match, nothing changes: no field and no section.bench/,pricing.ts, release scripts, the Action pin, golden workflows.Repro: stakwork/strut at
41fb967(parent of PR 11's mergeeda56a5), yarn 1.22.22Command, from the read-only clone (nothing pushed, no comments on that repo):
The key is a placeholder and the budget is $0.001, so no billed LLM call was possible.
Before (
origin/main5ffd256), exit 2, about 30 s, fails at install:After (this branch): install and resolution pass, and the run goes on through rules, assist and verification. It costs $0.00 and makes 0 agent edits. Verification then fails on strut's own tests, as expected, because no assisted fix ran:
Lockfile on the produced branch, with the manifest range kept:
There is no
resolvedVersionin that run'sreport.json, because target and resolved now match.Tests
fix/managers/resolved-version.test.ts(new), mocked installs for npm, pnpm and Yarn classic:resolvednote;resolved: [{ name, requested: '1.1.0', version: '1.1.3' }];Requested target@1.1.0, but . resolved 2.0.0: a different major than 1.1.0;~1.1.0→ 1.2.0) stops too.pinClassicPatterns: the moved pattern, a shared header, and no entry at the target.compatible-ranges.test.ts(UPTIDE_NETWORK=1, real registry): new Yarn classic case (a). React^19.0.0is locked at 19.1.0, the upgrade goes to 19.2.0, the lockfile and node_modules end at 19.2.0, the range stays^19.0.0, and a frozen install leaves the lockfile unchanged. With the pattern merge disabled, this case fails. The whole matrix passes:Tests 14 passed (14)(npm, pnpm, Berry 3/4, Yarn classic).report.test.ts) and terminal (format-fix.test.ts) each get a test for the new section.Local, on the final commit:
Not verified / open
run.ts(outside what the <pack> pack covers) has no test of its own.uptide.run/v1: "report.json" here means thefixresult (FixResult), which has no JSON schema file; its schema is the TypeScript type. The Action'suptide.run/v1report (schema/uptide.run.v1.json) is unchanged and has onlyto. Say ifresolvedVersionbelongs there too.resolutions: still written for transitive pins, as before; only the direct pattern is moved.🤖 Generated with Claude Code