Your AI coding assistant changes a dependency requirement, runs the app, and reports success. But did it run the dependency you now require? A successful command can conceal a stale lockfile if the command deliberately skips the consistency check.

This guide reproduces that mismatch with uv, then repairs it explicitly. The reusable drill runs in a disposable directory and records both the installed package version and lockfile hashes. It does not touch a business repository or call a model.

The fictional business problem

Orbit Studio is a fictional business building a Python intake helper with an AI coding assistant. Its developer changes an exact dependency requirement from one version to another. The acceptance question is narrow: does the run use a lock consistent with the requested version? This example uses the small idna package as a version probe, not as an AI feature or a recommendation to adopt either release.

Keep the pieces distinct. pyproject.toml states project requirements; uv.lock records the resolved dependency selection; .venv is the project environment. uv recommends version-controlling the lockfile. See Working on projects.

Locked and frozen answer different questions

According to uv’s locking and syncing documentation, --locked refuses an outdated lock rather than updating it. --frozen uses the lock without checking its consistency with current project metadata. uv lock --check checks that consistency. Frozen can be intentional when another trusted step already validated the pair; it is a poor substitute for that validation.

The following table reports this article’s actual local experiment, not a product benchmark or a measurement of business savings.

Fixture stage Observed result Lock bytes
Requirement idna 3.10; locked run Printed 3.10 Unchanged after run
Requirement edited to 3.11; lock check Rejected stale lock, exit 1 Unchanged
Edited requirement; locked run Rejected stale lock, exit 2 Unchanged
Edited requirement; frozen run Printed old 3.10 Unchanged
Explicit lock repair Resolved the edited requirement Changed
Locked run after repair Printed 3.11 Unchanged after run
Six observed behavioral checks on Windows, uv 0.11.3 and Python 3.14.3. Package numbers are deliberate fixture pins, not claims about current recommended versions.

Run the disposable drill

Prerequisites: Python 3.14 and uv available on PATH, plus network access to the public package index. This run used Python 3.14.3 and uv 0.11.3; the current documentation can describe newer releases. Install through an official uv installation method if needed. The drill does not install or upgrade uv or download a Python interpreter.

Save the following as lock_drill.py. It creates a project, private cache and virtual environment under one temporary directory, downloads the two pinned idna releases and imports them. It removes inherited uv overrides and disables configuration discovery for this fixture; it is not intended to model your company’s private-index setup. Python’s TemporaryDirectory context cleans up on normal context exit. Abrupt process termination can leave temporary files.

"""Disposable uv lock-drift experiment. Downloads two pinned idna releases."""
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile


def drill():
    uv = shutil.which('uv')
    if not uv:
        raise RuntimeError('Install uv from its official source first')
    logs = []
    with tempfile.TemporaryDirectory(prefix='orbit-lock-drill-') as folder:
        root = Path(folder)
        env = os.environ.copy()
        # Ignore inherited project/environment/index overrides for this fixture.
        for key in list(env):
            if key.startswith('UV_') or key in ('VIRTUAL_ENV', 'CONDA_PREFIX'):
                env.pop(key, None)
        env['UV_PYTHON_DOWNLOADS'] = 'never'
        env['UV_CACHE_DIR'] = str(root / '.cache')

        def run(args, succeeds=True):
            command = [uv, '--no-config', *args]
            result = subprocess.run(command, cwd=root, env=env,
                                    capture_output=True, text=True, timeout=180)
            logs.append({'args': args, 'returncode': result.returncode,
                         'stdout': result.stdout, 'stderr': result.stderr})
            if (result.returncode == 0) != succeeds:
                raise RuntimeError(json.dumps(logs[-1], indent=2))
            if not succeeds and 'needs to be updated' not in result.stderr:
                raise RuntimeError('Failure was not the expected stale-lock refusal')
            return result.stdout.strip()

        def manifest(version):
            (root / 'pyproject.toml').write_text(
                '[project]\nname = "orbit-lock-drill"\nversion = "0.1.0"\n'
                'requires-python = ">=3.14,<3.15"\n'
                f'dependencies = ["idna=={version}"]\n', encoding='utf-8')

        def digest():
            return hashlib.sha256((root / 'uv.lock').read_bytes()).hexdigest()

        probe = ['python', '-c', 'import idna; print(idna.__version__)']
        version = run(['--version'])
        manifest('3.10')
        run(['lock', '--python', sys.executable])
        initial = digest()
        assert run(['run', '--locked', *probe]) == '3.10'
        assert digest() == initial
        manifest('3.11')
        run(['lock', '--check'], succeeds=False)
        assert digest() == initial
        run(['run', '--locked', *probe], succeeds=False)
        assert digest() == initial
        assert run(['run', '--frozen', *probe]) == '3.10'
        assert digest() == initial
        run(['lock'])
        repaired = digest()
        assert repaired != initial
        assert run(['run', '--locked', *probe]) == '3.11'
        assert digest() == repaired
        return {'passed': True, 'uv': version, 'python': sys.version,
                'checks': ['baseline uses 3.10', 'check rejects stale lock',
                           'locked run rejects drift', 'frozen still uses 3.10',
                           'explicit lock changes bytes', 'repaired run uses 3.11'],
                'initial_lock_sha256': initial, 'repaired_lock_sha256': repaired,
                'logs': logs}


if __name__ == '__main__':
    print(json.dumps(drill(), indent=2))

Run it with assertions enabled, using the same Python 3.14 interpreter you want uv to use:

python -X utf8 lock_drill.py

Successful output is a JSON record with passed: true, the uv/Python versions, six named checks, two lock hashes and each command’s stdout, stderr and exit code. Timings and temporary paths vary. The two refusal checks also require the stale-lock error text: an unrelated network failure must not count as a successful drift test. A missing uv executable, missing package download or unexpected version stops the drill.

The final HTML’s script was extracted and this documented command was executed again in a disposable reader directory. The six behavioral checks passed. This verifies the local command behavior and package versions, not an AI assistant’s reasoning.

Give the coding assistant a stronger acceptance contract

Use this task note when an assistant edits Python dependencies: “Explain why the dependency change is needed. Update the manifest and resolve the lock intentionally. Show both diffs. Run the lock consistency check and the relevant application tests using a locked run. Report actual versions, exit codes and any checks you could not execute. Do not switch to frozen to hide a failed consistency check.”

That note is an instruction for review, not a permission mechanism or a tested integration with Codex, Claude or Antigravity. In an existing repository, adapt commands to its supported Python version, extras, dependency groups and project configuration. Do not paste the fixture’s configuration isolation into a private-index deployment without reviewing its consequences.

A useful pull request contains the dependency purpose, manifest/lock changes, the exact test commands and their results. If a lock check fails unexpectedly, preserve the diff before investigating. Repair with an intentional resolution and review what changed; repeatedly regenerating a lock until the error disappears is weak evidence. For isolating an assistant’s changes, pair this with a dedicated Git worktree. For an already broken app, use the regression-finding drill.

What this gate still cannot prove

A consistent lock does not establish that the dependency is trustworthy, licensed appropriately for your use, free of vulnerabilities or correct for your business. The example imports a version string; it does not exercise an intake application, model SDK, production host, CI runner or multiple operating systems. Add behavior tests for the actual feature and review package provenance separately.

Locked runs can still create or synchronize an environment and require package access. They are not read-only filesystem operations. Also, uv documents that ordinary uv run retains extra installed packages by default; exact synchronization is a separate concern. Test in the intended clean environment before treating a developer machine’s success as deployment evidence.

Pin the uv/Python versions you validated in your own build process and keep that choice visible. New package releases alone do not necessarily make a lock stale. This drill deliberately excludes the previously locked release with a changed exact requirement so the inconsistency is unambiguous.

Jason Ead publishes AlgoThoughts AI News and connects practical engineering decisions with My Biz Heroes: websites, automation, workflows, AI tools and training. Explore Jason’s work. The cover is generated conceptual artwork; the interior table records actual fixture results.