quaint-telephone-89068
07/01/2026, 7:03 AMFROM $ARG base images — one whose default is an in-repo docker_image target address and one whose default is a plain registry reference — Pants substitutes the built base image onto the wrong build arg, and leaves the real address-valued arg unsubstituted. Its literal //path:target default then leaks to the Docker build:
failed to solve: failed to parse stage name "//path:target": invalid reference format
Reproduce
# src/upstream/BUILD
docker_image(
name="image",
repository="upstream/{name}",
image_tags=["1.0"],
instructions=["FROM alpine:3.16.1"],
)
# src/downstream/BUILD
docker_image(name="image")
# src/downstream/Dockerfile
ARG AAA_LITERAL=registry.example.com/base:1.0
ARG ZZZ_UPSTREAM=src/upstream:image
FROM $AAA_LITERAL AS first
FROM $ZZZ_UPSTREAM
pants package src/downstream:image
Expected: ZZZ_UPSTREAM is substituted with the built upstream/image:1.0, and AAA_LITERAL is left as a normal literal (as in test_from_image_build_arg_not_in_repo_issue_15585).
Actual: Pants emits --build-arg AAA_LITERAL=upstream/image:1.0 (wrong arg) and no --build-arg ZZZ_UPSTREAM, so ZZZ_UPSTREAM keeps its src/upstream:image default and Docker fails with invalid reference format.
Root cause
In src/python/pants/backend/docker/util_rules/docker_build_context.py (create_docker_build_context):
1. Both args are classified as "from image" build args — the literal registry ref also matches the loose valid_address regex in subsystems/dockerfile_wrapper_script.py.
2. from_image_build_args is stored sorted by "NAME=VALUE" (KeyValueSequenceUtil.from_strings → sorted(...) in backend/docker/utils.py), so AAA_LITERAL sorts before ZZZ_UPSTREAM.
3. resolve_unparsed_address_inputs(..., skip_invalid_addresses=True) drops the non-resolvable literal, shortening the resolved-address list.
4. The result is then zipped against the full, sorted arg names:
from_image_build_args = [
f"{arg_name}={address_to_built_image_tag[addr]}"
for arg_name, addr in zip(dockerfile_build_args.keys(), from_image_addresses)
]
Because a non-last value was dropped, zip mispairs the names with the addresses: the upstream image lands on the sorted-first AAA_LITERAL, and ZZZ_UPSTREAM is silently dropped.
Any Dockerfile where an earlier-sorting FROM-image build arg is a non-resolvable value (a plain image ref, or an address not in the repo) hits this. The COPY-args path (fill_args_from_copy, same file) uses the same zip(keys(), resolved_addresses) pattern and appears to have the same latent misalignment, though I haven't reproduced that one.
Pants version
Reproduced on 2.31.0 and on main (the relevant code — classification, sorted() storage, and the zip — is identical on both).
OS
Both — this is pure resolution/build-arg logic, independent of platform (reproduced on macOS arm64).
Additional info
I have a PR with a failing reproducer test (test_from_image_build_arg_mixed_address_and_literal in docker_build_context_test.py) plus a fix that resolves each build-arg value independently so a skipped value can't shift the alignment, keyed by name:
#23471
pantsbuild/pantsquaint-telephone-89068
07/07/2026, 3:43 PM