← Back to issue list

`stage-packages` is nondeterministic

View original Github issue

Metadata

Project
craft-parts
Number
#1751
Type
issue
State
closed
Author
carlcsaposs-canonical
Labels
—
Created
Updated
Closed

Current evaluation

Fixed by preserving user-specified stage-package order before APT marking; commit 26f4b35e uses list(dict.fromkeys(package_names)), removing hash-seed-dependent dependency selection.

Resolution: closed resolved

Reason: Commit 26f4b35e deduplicates package names with list(dict.fromkeys(package_names)) and passes that ordered list to AptCache.mark_packages. This ensures locales-all is marked before postgresql-18, so APT consistently selects the intended alternative dependency.

Evaluated by: mr-cal-bot

No scores available.

Issue body

### Bug Description `stage-packages` (with identical contents) installs different packages on different invocations of *craft pack #### Context The `postgresql-18` deb package depends on `locales | locales-all` #### Expected behavior There is a way to stage the `locales-all` and `postgresql-18` packages without staging the `locales` package Either with ```yaml stage-packages: - locales-all - postgresql-18 ``` or ```yaml parts: a: plugin: nil stage-packages: - locales-all b: after: - a stage-packages: - postgresql-18 ``` #### Actual behavior ```yaml stage-packages: - locales-all - postgresql-18 ``` sometimes stages the `locales` package and sometimes does not ### To Reproduce ```shell mkdir foo cd foo cat > rockcraft.yaml << 'EOF' name: repro version: '1.0' title: repro summary: foo description: foo base: bare build-base: ubuntu@26.04 platforms: amd64: arm64: parts: foo: plugin: nil stage-packages: - locales-all - postgresql-18 EOF for i in $(seq 1 6); do rm -rf *.rock unpack contents.txt rockcraft clean rockcraft pack mkdir unpack rock=$(ls *.rock) tar -xf "$rock" -C unpack for blob in unpack/blobs/sha256/*; do tar -tf "$blob" 2>/dev/null; done > contents.txt if grep -q 'usr/sbin/locale-gen' contents.txt; then echo "run $i: locales is in the rock" else echo "run $i: locales is NOT in the rock" fi done ``` Example output ``` Packed repro_1.0_arm64.rock run 1: locales is NOT in the rock Packed repro_1.0_arm64.rock run 2: locales is in the rock Packed repro_1.0_arm64.rock run 3: locales is in the rock Packed repro_1.0_arm64.rock run 4: locales is NOT in the rock Packed repro_1.0_arm64.rock run 5: locales is NOT in the rock Packed repro_1.0_arm64.rock run 6: locales is NOT in the rock ``` #### Simplified steps to reproduce, without rockcraft ```shell lxc launch ubuntu:26.04 repro lxc shell repro apt update apt install python3-pip python3-apt -y pip install craft-parts --break-system-packages cat > repro.py << 'EOF' import os import pathlib import craft_parts.packages.deb import shutil craft_parts.packages.deb.Ubuntu.configure("foo") names = {"locales-all", "postgresql"} # deb.py:907 does `set(package_names)` out = pathlib.Path("/tmp/out") if out.exists(): shutil.rmtree(out) out.mkdir() result = craft_parts.packages.deb.Ubuntu.fetch_stage_packages(cache_dir=pathlib.Path("/tmp/cache"), package_names=names, stage_packages_path=out, base="bare", arch="amd64") print(f"PYTHONHASHSEED={os.environ.get('PYTHONHASHSEED')} -> locales fetched:", any(package.startswith("locales=") for package in result)) EOF for seed in 0 1 2 3 4 5 6 7; do PYTHONHASHSEED=$seed python3 repro.py; done ``` Example output ``` PYTHONHASHSEED=0 -> locales fetched: False PYTHONHASHSEED=1 -> locales fetched: True PYTHONHASHSEED=2 -> locales fetched: False PYTHONHASHSEED=3 -> locales fetched: False PYTHONHASHSEED=4 -> locales fetched: True PYTHONHASHSEED=5 -> locales fetched: True PYTHONHASHSEED=6 -> locales fetched: False PYTHONHASHSEED=7 -> locales fetched: False ``` ### part yaml ```shell name: repro version: '1.0' title: repro summary: foo description: foo base: bare build-base: ubuntu@26.04 platforms: amd64: arm64: parts: foo: plugin: nil stage-packages: - locales-all - postgresql-18 ``` ### Relevant log output ```shell - ```

Evaluation history

Date Model Scores Action Summary
stealth/space-bunny-alpha — closed resolved Fixed by preserving user-specified stage-package order before APT marking; commit 26f4b35e uses list(dict.fromkeys(package_names)), removing hash-seed-dependent dependency selection.
stealth/space-bunny-alpha — closed resolved Fixed in canonical/craft-parts#1752, commit 26f4b35e, making stage-packages deterministic by eliminating hash-order-dependent package marking.
qwen/qwen3.8-27b
Impact: 60
Quick Win: 34
Complexity: 35
Confidence: 85
Actionability: 88
needs triage stage-packages is nondeterministic: deb.py:907 turns names into a set and AptCache.mark_packages iterates it, so apt marks packages in hash-dependent order; alternative deps (locales|locales-all) resolve differently per run. Valid, reproducible, unfixed.
qwen/qwen3.8-27b
Impact: 60
Quick Win: 38
Complexity: 30
Confidence: 85
Actionability: 90
needs triage stage-packages nondeterminism: apt OR-dependency resolution (locales | locales-all) picks different providers per run because mark_packages iterates a Python set whose order varies with PYTHONHASHSEED. Root cause pinpointed at deb.py:907; new, unlabelled.
qwen/qwen3.8-27b
Impact: 60
Quick Win: 43
Complexity: 20
Confidence: 85
Actionability: 90
needs triage stage-packages nondeterminism: mark_packages iterates a Python set, so apt resolves alternative deps (locales vs locales-all) differently per run. Root cause confirmed at deb.py:907; unfixed, unlabeled, no maintainer response.
qwen/qwen3.8-27b
Impact: 60
Quick Win: 28
Complexity: 45
Confidence: 85
Actionability: 85
needs triage stage-packages fetch is nondeterministic: deb.py turns the ordered package list into a set, so apt marks packages in hash-randomized order and picks different alternatives (locales vs locales-all) per run. Unlabelled, no maintainer response.

Update history

Date Change
closed
updated
created

Related issues

Issue Project State Summary Similarity
#1752 fix: Make `stage-packages` deterministic craft-parts merged Merged PR #1752 in commit 26f4b35e, preserving stage-packages input order during deduplication and APT marking to make resolution deterministic; fixes #1751.
72%