Skip to content

uv: lock() .run target for uv.lock omits existing lock and writes to runfiles #4339

Open

Description

@rickeylev

Running bazel run <name>.run on a lock() target with a uv.lock output
(e.g. bazel run //dev:uv_lock.run -- --upgrade-package certifi==2026.1.4)
fails to update the workspace lockfile in place due to two bugs:

  1. Missing existing lock in runfiles: _common_lock in
    python/uv/private/lock.bzl passes ctx.files.existing_output to the build
    action's inputs, but omits it from _RunLockInfo.runfiles. Because uv
    runs in .runfiles/_main where no uv.lock is present, uv lock
    re-resolves all dependencies from scratch instead of incrementally updating
    the existing lock.
  2. Output written to .runfiles instead of the source tree: Unlike
    uv pip compile (which accepts --output-file), uv lock always reads and
    writes uv.lock in the project directory relative to its working directory.
    When BUILD_WORKSPACE_DIRECTORY is set, uv_lock.sh and uv_lock.bat
    execute uv immediately without linking or copying {{project_lock}} to
    ${BUILD_WORKSPACE_DIRECTORY}/{{src_out}}. The updated lockfile is written
    to bazel-bin/.../*.run.runfiles/_main/.../uv.lock, leaving the source tree
    untouched.

Reproduction

#!/usr/bin/env bash
set -euo pipefail

git checkout -- dev/uv.lock
bazel run //dev:uv_lock.run -- --upgrade-package certifi==2026.1.4

changes=$(git diff dev/uv.lock | grep -c '^-version = ' || true)
if [[ "$changes" -eq 1 ]]; then
    echo "PASS: dev/uv.lock updated 1 package as expected"
else
    echo "FAIL: expected 1 package updated in dev/uv.lock, got $changes"
    exit 1
fi

Running this prints:

FAIL: expected 1 package updated in dev/uv.lock, got 0

while bazel-bin/dev/uv_lock.run.runfiles/_main/dev/uv.lock contains a full
re-resolution of 18 packages.

Activity

  1. rickeylev commented on Oct 24, 2026

    @rickeylev
    CollaboratorAuthor

    This is a bug, right? I noticed that the integration test is verifying that the file in runfiles is being updated. Which I find rather strange -- runfiles are typically read-only, so writing to such a file is unsound.

    Fixing this might be tricky, though. AFAICT uv lock doesn't have a flag to control where output goes. But weren't we planning on synthesizing a folder structure in runfiles so workspace could work, and so project files from other repos could be integrated? Maybe we'll have to change it to create a temporary directory at runtime and operate within that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions