Targeted preview intermittently reports duplicate URN after removing an untargeted dependency edge
What happened?
pulumi preview --target intermittently reports Duplicate resource URN for an existing, untargeted resource after a dependency edge is removed elsewhere in the program. After emitting the diagnostic, the pulumi preview command remains alive until interrupted.
I reduced this from a production targeted-update failure to a disposable local DIY-backend preview. The reproduction uses no cloud resources. Before and after the failed preview, the exported snapshot contains 7 resources, 7 unique URNs, and 0 pending operations.
This reproduces with both Pulumi CLI 3.255.0 and 3.256.0. The clean 3.256.0 stack used Python SDK 3.256.0 and pulumi-random 4.21.1 from its first update, so no CLI, SDK, or provider-version transition is involved.
Minimal program
Pulumi.yaml:
name: pulumi-target-duplicate-repro
runtime:
name: python
options:
virtualenv: venvrequirements.txt:
pulumi==3.256.0
pulumi-random==4.21.1__main__.py:
import pulumi
import pulumi_random as random
config = pulumi.Config()
capture = config.get_bool("capture") or False
rollback = [
random.RandomString(f"rollback-{index}", length=12, special=False)
for index in range(3)
]
target = random.RandomString("target", length=12, special=False)
random.RandomString(
"snapshot-marker",
length=12,
special=False,
opts=pulumi.ResourceOptions(depends_on=rollback if capture else []),
)
pulumi.export("target", target.result)Reproduction with an exact 3.256.0 CLI
Download the official 3.256.0 archive for your platform, verify it against the release checksum, extract it, and set PULUMI to that exact binary. For example, after extraction on Linux x86_64:
export PULUMI="$PWD/pulumi-3.256.0/pulumi"
test "$("$PULUMI" version)" = 'v3.256.0' || {
printf 'expected Pulumi v3.256.0\n' >&2
exit 1
}
export PULUMI_CONFIG_PASSPHRASE='local-repro-only'
python3 -m venv venv
venv/bin/pip install -r requirements.txt
mkdir backend
"$PULUMI" login "file://$PWD/backend"
"$PULUMI" stack init exact256 --secrets-provider passphrase
"$PULUMI" config set capture true --stack exact256
"$PULUMI" up --stack exact256 --yes --non-interactive
# The state still contains the three old dependencies. This changes only the
# graph returned by the next program evaluation; it is deliberately not applied.
"$PULUMI" config set capture false --stack exact256
"$PULUMI" stack export --stack exact256 | jq -e '
(.deployment.resources | length) == 7 and
([.deployment.resources[].urn] | unique | length) == 7 and
((.deployment.pending_operations // []) | length) == 0'
target='urn:pulumi:exact256::pulumi-target-duplicate-repro::random:index/randomString:RandomString::target'
for n in $(seq 1 50); do
printf 'targeted preview %s\n' "$n"
timeout --signal=INT --kill-after=5s 20s \
"$PULUMI" preview --stack exact256 --target "$target" \
--non-interactive --color never
rc=$?
if [ "$rc" -eq 0 ]; then
continue
fi
printf 'targeted preview %s exited %s\n' "$n" "$rc"
break
done
"$PULUMI" stack export --stack exact256 | jq -e '
(.deployment.resources | length) == 7 and
([.deployment.resources[].urn] | unique | length) == 7 and
((.deployment.pending_operations // []) | length) == 0'Run this only against the disposable local backend: the timeout may send SIGKILL if the command ignores SIGINT, which could leave a backend lock.
The initial update reports six creates (the stack plus five RandomString resources); the checkpoint also contains the provider resource, for seven entries total. Changing capture to false removes the explicit dependency edges only from the next program evaluation; it does not modify the checkpoint because no non-targeted update is applied. The preview targets only the unchanged target resource.
On my clean 3.256.0 stack the first failure appeared on targeted preview 23:
random:index:RandomString rollback-2 error: Duplicate resource URN
'urn:pulumi:exact256::pulumi-target-duplicate-repro::random:index/randomString:RandomString::rollback-2';
try giving it a unique name
Resources:
5 unchanged
1 erroredThe preview command remained alive until the 20-second timeout sent SIGINT. An A/B stack created and exercised with the 3.255.0 CLI first failed on targeted preview 15. Twenty consecutive non-targeted 3.256.0 previews completed successfully:
for n in $(seq 1 20); do
printf 'full preview %s\n' "$n"
timeout --signal=INT --kill-after=5s 20s \
"$PULUMI" preview --stack exact256 --non-interactive --color never || break
doneExpected behavior
Targeting the unchanged target should not report a duplicate URN for an existing untargeted rollback-* resource. The preview should either complete successfully or fail deterministically with a targeted-update contract error. The pulumi preview command should exit after an engine diagnostic.
Additional context
- Pulumi 3.256.0 contains #24108, but that change handles
Applyover outputs from skipped creates. This reproduction still reports a duplicate URN involving an existing untargeted resource. - This may be the same engine defect described in the latest comment on #20852: a targeted operation while the program removes a dependency edge outside the target set, followed by a duplicate on an unrelated bystander. This report adds a minimal cloud-provider-independent program using only
pulumi-randomand a preview-only variant whose checkpoint remains healthy; the #20852 cases used targetedupand left corrupt snapshots. - The original production
pulumi up --targetfailed with the same duplicate-URN diagnostic while writing a DIY-backend snapshot. This issue deliberately uses preview and a disposable local backend to avoid mutating cloud resources.
Environment
- Ubuntu 24.04 x86_64
- Python 3.12
- Pulumi CLI 3.255.0 and 3.256.0
- Pulumi Python SDK 3.256.0
- pulumi-random 4.21.1
Source: pulumi/pulumi