Skip to content

Don't reproduce frozen stages with --force - #11084

Open
arose26 wants to merge 1 commit into
treeverse:mainfrom
arose26:fix/frozen-stage-force-repro
Open

Don't reproduce frozen stages with --force#11084
arose26 wants to merge 1 commit into
treeverse:mainfrom
arose26:fix/frozen-stage-force-repro

Conversation

@arose26

@arose26 arose26 commented Aug 18, 2026

Copy link
Copy Markdown

Fixes #11004

dvc repro --force rewrites a frozen stage's dependency hashes in dvc.lock, while plain dvc repro leaves them alone. Using the reproducer from the issue:

baseline         second_stage foo.py md5 = 7423db4d3117e33e9762e1496023ac70
after repro      second_stage foo.py md5 = 7423db4d3117e33e9762e1496023ac70
after repro -f   second_stage foo.py md5 = 1a6c3a03dc0c3289cd3c825982243c37

The stage is still frozen: true throughout, and its command is never re-run — only the recorded dependency hashes move. That leaves the lock no longer describing what the stage was actually built against, which is the thing freezing is for. The dvc freeze docs say a frozen stage is considered unchanged, so --force reproducing it reads as a bug rather than intended behaviour.

Cause

Stage.reproduce() short-circuits every skip check when force is set:

force = kwargs.get("force", False)
...
if force:
    pass
elif not self.changed(allow_missing, upstream):
    ...
    return None

Without --force a frozen stage exits through that elifchanged_deps() returns False for a frozen stage — so _reproduce_stage never calls stage.dump() and the lock is untouched. With --force it falls through to self.run(...). run() still refuses to execute the command (elif not self.frozen and self.cmd), but the stage is re-saved afterwards and the lock is rewritten.

Change

force is neutralised for frozen non-import stages, so they go through the same checks as an unforced run.

It deliberately does not return None outright. changed() is changed_stage() or changed_deps() or changed_outs(), and only the middle one is short-circuited by frozen — so a frozen stage whose outputs are missing still reaches run() and restores/verifies them, exactly as it does today without --force. Import stages are excluded, matching the existing stage.frozen and not stage.is_import condition in _reproduce_stage and the import handling in run().

Tests

test_repro_frozen_force freezes a stage, changes an upstream dependency, runs reproduce(force=True), and asserts both that nothing is reproduced and that the stage's dvc.lock entry is byte-identical. It fails on main and passes with this change.

tests/func/repro/ and tests/unit/stage/ pass: 164 passed. Two failures in tests/unit/stage/test_fill_from_lock_use_appropriate_checksum and test_dump_nondefault_hash — fail identically on main without this change, so they are pre-existing and unrelated. ruff check and ruff format --check are clean on the changed files (the one remaining too-many-positional-arguments in dvc/stage/__init__.py is pre-existing and untouched).

Forcing reproduction rewrote a frozen stage's dependency hashes in dvc.lock
even though its command is never re-run, so the lock stopped recording what
the stage was actually built against.
@github-project-automation github-project-automation Bot moved this to Backlog in DVC Aug 18, 2026
@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.98%. Comparing base (2431ec6) to head (9f525db).
⚠️ Report is 213 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11084      +/-   ##
==========================================
+ Coverage   90.68%   90.98%   +0.30%     
==========================================
  Files         504      505       +1     
  Lines       39795    41149    +1354     
  Branches     3141     3264     +123     
==========================================
+ Hits        36087    37439    +1352     
- Misses       3042     3071      +29     
+ Partials      666      639      -27     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

force repro updates hashsums of frozen stages' dependencies

1 participant