Skip to content

Swift: updating one dependency rewrites unrelated transitive Package.resolved pins to incompatible versions #44312

Description

@RahulGautamSingh

Discussed in #44311

Originally posted by fxwx23 June 30, 2026

How are you running Renovate?

A Mend.io-hosted app

Which platform you running Renovate on?

GitHub.com

Which version of Renovate are you using?

43.242.2

Please tell us more about your question or problem

Hi! First, thanks a lot for the recent work on Swift Package.resolved support (#41534) — it's been really helpful.

I ran into some behavior I wanted to check before assuming it's a bug. After a dependency update, Renovate seems to rewrite a dependency's pin in every Package.resolved in the repo, including separate packages where that library is only a transitive pin on a different (incompatible) version range. Because the pin is rewritten directly without running swift package resolve, the result can be a version that the other package doesn't actually allow. I wasn't sure whether this is intended, so I thought I'd ask here first.

What I'm seeing

A repo with two independent SwiftPM packages:

  • a/Package.swift depends on Yams directly (exact: "6.2.1") → a/Package.resolved pins yams 6.2.1.
  • b/Package.swift doesn't mention Yams at all; it depends on XcodeGen 2.45.4, which pulls Yams transitively with from: "5.0.0" (i.e. <6.0.0) → b/Package.resolved pins yams 5.0.1.

Renovate opens a PR to bump Yams in a to 6.2.2. The PR's dependency table lists only that single update, but the diff also changes b/Package.resolved: yams goes 5.0.16.2.2, which XcodeGen 2.45.4 forbids. Running swift package resolve in b puts it back to a 5.x version, so the value written there appears to be incorrect. I'd have expected b to be left untouched.

Where it seems to come from

In lib/modules/manager/swift/artifacts.ts, findPackageResolvedFiles() collects every Package.resolved in the repo, and pins are matched by URL only. The only guard is "skip if already at the target version" (pin.state.version === resolvedVersion), so there doesn't seem to be anything checking which package declared the dep, or whether the new version is allowed in that particular resolved file.

This looks like a different symptom from the other recent reports about this feature (#41780 for the v prefix, #42186 / #42188 for from: ranges) — those are about the same pin's value being malformed, whereas here a pin in an unrelated package is being changed.

A possible direction (for exact specs)

One idea that would cover the case above: for exact (isSingleVersion) specs, only rewrite a pin when its current version matches the version being upgraded from:

if (dep.currentValue && versioningApi.isSingleVersion(dep.currentValue)) {
  if (pin.state.version !== dep.currentVersion) {
    continue; // not the pin being upgraded from — leave it alone
  }
}

In the example, a's pin (6.2.1) matches currentVersion and updates, while b's pin (5.0.1) doesn't and is skipped. A pin already at 6.2.1 elsewhere still updates, so this wouldn't regress the repo-wide sweep that Xcode workspaces seem to rely on (their Package.resolved lives under *.xcworkspace/... and isn't a manifest sibling, so scoping purely by path doesn't look viable — see #9735). It also needs no Swift toolchain, and the isSingleVersion gate is in the same spirit as the guard discussed in #42188.

The part I'm unsure about: range / from: specs

I don't think this idea extends cleanly to range specs, and I'd appreciate guidance. Since the swift manager never reads Package.resolved, config.lockedVersion is unset and currentVersion ends up derived from the manifest range + published releases (getCurrentVersion), not from the pin. So a plain equality check would skip legitimately-owned range pins (false negative), while a matches(pin, range) check would still over-match the same library's transitive pins in unrelated packages. Handling ranges correctly seems to need some association between a Package.resolved and the manifest(s) that own it (related to #9735), which feels bigger than this.

Is the cross-package rewrite considered expected behavior, or worth treating as a bug? If it's the latter, I'd be happy to open a PR with the exact-spec guard above plus tests (the transitive/unrelated-package case and the Xcode-workspace case), and leave the range case for discussion. Thanks!

Refs: #41534, #41780, #42186, #42188, #9735.

Logs (if relevant)

Logs

Replace this text with your logs, between the starting and ending triple backticks

Metadata

Metadata

Assignees

No one assigned

    Labels

    manager:swiftRelated to the Swift package manager

    Type

    Priority

    Medium

    Regression introduced in

    None yet

    Datasource

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions