#7057·pixi

Multi-package pixi publish aborts entire batch on first 409 (already-exists), instead of skipping that package

Author: TomNysWFCreated Sep 18, 2026Updated Sep 18, 2026

Checks

  • I have checked that this issue has not already been reported.
  • I have confirmed this bug exists on the latest version of pixi, using pixi --version.

(Tested against pixi 0.79.0.)

Reproducible example

Commands I ran and their output:

bash
$ pixi --version
pixi 0.79.0

$ pixi publish --target-channel quetz://<server>/<channel>
 Publishing 86 workspace packages that set `publish = true`

 Building 86 package(s):
  - <pkg-a> v1.4.0 [<build>] (linux-64)
  - <pkg-b> v1.0.0 [<build>] (linux-64)
  ... (84 more)

 Publishing 86 package(s) to channel quetz://<server>/<channel>
 INFO pixi_cli::publish: Uploading packages to Quetz: quetz://<server>/<channel>
 INFO rattler_upload::upload: Uploading package file: <pkg-a>-1.4.0-<build>.conda (7.22 KiB)

Error:   × Server responded with error
  ╰─▶ HTTP status client error (409 Conflict) for url (https://<server>/api/channels/<channel>/upload/<pkg-a>-1.4.0-<build>.conda?force=false&sha256=<hash>)

Already published at this version and build number (409 Conflict).

All 86 packages built successfully first. The upload step then attempted exactly one file (<pkg-a>, unrelated to and unchanged from any of the other 85), got a 409 because that one artifact already exists at that version+build, and the entire publish aborted right there. None of the other 85 packages - including several that had genuinely new/changed content - were ever attempted.

pixi.toml file that reproduces my issue:

toml
[workspace]
name = "example"
channels = ["conda-forge"]
platforms = ["linux-64"]

[feature.jazzy]
# ... publishes multiple pixi-build-ros member packages with publish = true

(Any workspace publishing more than one package in a single pixi publish call, where at least one already exists unchanged on the target and at least one other has new content, reproduces this.)

pixi info output:

System
------------
       Pixi version: 0.79.0
        TLS backend: rustls
           Platform: linux-64

Other files: none needed beyond a multi-package workspace where one member's build already exists on the target channel.

Issue description

pixi publish --help documents:

Packages that already exist at the target are skipped by default. With this flag [--no-skip-existing] they fail the publish instead...

This implies that when publishing a batch of packages, one already existing at the target should simply be skipped, and the rest of the batch should still be published. In practice, for a Quetz target, nothing is skipped client-side - the CLI just attempts the upload, gets a 409 from the server, and treats that as fatal for the entire remaining batch, even though only one package (out of many) actually conflicted.

Concretely, in crates/rattler_upload/src/upload/mod.rs, upload_package_to_quetz uploads package files one at a time in a loop and calls send_request_with_retry(...).await?; - the ? propagates the first non-2xx response (409 included) straight out of the function, aborting the loop and every package after it, rather than logging/skipping that one file and continuing.

This means in a workspace with many packages, a single stale/unrelated already-published artifact permanently blocks every future full-workspace publish, even for packages with genuinely new content, until that one artifact's version or build number is bumped (or the CLI's advertised default skip-existing behavior is actually implemented for Quetz).

Expected behavior

For a Quetz (and any other non-client-side-skip-checked) target, pixi publish of a multi-package batch should skip an individual package that already exists at the same version/build (409 response) and continue uploading the rest of the batch, matching the documented default ("skipped by default") - rather than aborting the whole publish on the first such conflict. --no-skip-existing should be the only way to make an existing-package conflict fatal, as the help text already implies.