Skip to content

dvc repro: rolls back .gitignore entries for successful stages when a later stage fails #11091

Description

@joostmeulenbeld

Bug Report

Description

When dvc repro runs a pipeline with multiple stages and a later stage fails, .gitignore entries added for outputs of earlier successfully completed stages are rolled back.

For comparison, when the same pipeline completes successfully, DVC correctly creates output/.gitignore containing:

/out1
/out2

If stage2 is changed to fail after creating its output, the files are still created, dvc.lock is updated, but the .gitignore entries are not created. After fixing the stage2 command and rerunning dvc repro, the outs of stage2  are correctly added to the .gitignore but stage1 .

The way I fix that now is by manually typing the succesfully run outputs into the .gitignore, which is cumbersome. Maybe there is a command to do it that I don't know about? But anyway, to me it would make more sense if the 

Reproduce

Start in an empty Git repository:

$ git init
$ uvx dvc init

Create the following dvc.yaml:

stages:
  stage1:
    outs: [output/out1]
    cmd:
      - mkdir -p output
      - echo ok > output/out1

  stage2:
    deps: [output/out1]
    outs: [output/out2]
    cmd:
      - cp output/out1 output/out2
      - exit 1

Run:

$ uvx dvc repro dvc.yaml

As expected, dvc repro fails because of exit 1.

Both output files have nevertheless been created:

$ ls output
out1  out2

However, the ignore rules for the outputs are not created in output/.gitignore. When running with -v it states explicitly that the .gitignore entry of out1 is removed. I guess this technically means this is not a bug but intended behavior, but it is counterintuitive and counterproductive for normal use.

As a control, removing the following command from stage2:

- exit 1

and running the same pipeline successfully (in a clean repo state) results in:

/out1
/out2

being added to output/.gitignore.

Expected

The .gitignore entry for output/out1 should be created. stage1 completed successfully, its output remains in the workspace, and the dvc.lock file is updated succesfully. A failure in the downstream stage2 should not roll back Git ignore state belonging to the already completed stage.

Environment information

Output of dvc doctor:

$ uvx dvc doctor
DVC version: 3.67.1 (pip)
-------------------------
Platform: Python 3.14.2 on Linux-7.1.5-76070105-generic-x86_64-with-glibc2.39
Subprojects:
        dvc_data = 3.18.3
        dvc_objects = 5.2.0
        dvc_render = 1.0.2
        dvc_task = 0.40.2
        scmrepo = 3.6.2
Supports:
        http (aiohttp = 3.14.3, aiohttp-retry = 2.9.1),
        https (aiohttp = 3.14.3, aiohttp-retry = 2.9.1)
Config:
        Global: /home/joostmeulenbeld/.config/dvc
        System: /etc/xdg/dvc
Cache types: hardlink, symlink
Cache directory: ext4 on /dev/mapper/data_bb3Cv-root
Caches: local
Remotes: None
Workspace directory: ext4 on /dev/mapper/data_bb3Cv-root
Repo: dvc, git
Repo.site_cache_dir: /var/tmp/dvc/repo/3fb681d8dcf172821499f0051802062e

Additional Information (if any):

`dvc repro -v` run
2026-09-01 14:00:09,035 DEBUG: v3.67.1 (pip), CPython 3.14.2 on Linux-7.1.5-76070105-generic-x86_64-with-glibc2.39
2026-09-01 14:00:09,035 DEBUG: command: /home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/bin/dvc repro -v
2026-09-01 14:00:09,412 DEBUG: Output 'output/out1' of stage: 'stage1' changed because it is 'not in cache'.
2026-09-01 14:00:09,412 DEBUG: stage: 'stage1' changed.
2026-09-01 14:00:09,415 DEBUG: Removing output 'output/out1' of stage: 'stage1'.
2026-09-01 14:00:09,415 DEBUG: Removing '/home/joostmeulenbeld/git/mwe_dvc_noignore/output/out1'
Running stage 'stage1':
> mkdir -p output
> echo ok > output/out1
2026-09-01 14:00:09,428 DEBUG: Added '/home/joostmeulenbeld/git/mwe_dvc_noignore/output/out1' to gitignore file.
2026-09-01 14:00:09,437 DEBUG: Computed stage: 'stage1' md5: 'fc98065f95e8b773dcd45d4d3e4fb0f8'                                                                                                                                                    
2026-09-01 14:00:09,441 DEBUG: Preparing to transfer data from 'memory://dvc-staging-md5/cc0bb25b96409904853d0280d6caaf899e5e170c9701e667d661ed11eed04c8e' to '/home/joostmeulenbeld/git/mwe_dvc_noignore/.dvc/cache/files/md5'                    
2026-09-01 14:00:09,441 DEBUG: Preparing to collect status from '/home/joostmeulenbeld/git/mwe_dvc_noignore/.dvc/cache/files/md5'
2026-09-01 14:00:09,441 DEBUG: Collecting status from '/home/joostmeulenbeld/git/mwe_dvc_noignore/.dvc/cache/files/md5'
2026-09-01 14:00:09,442 DEBUG: Preparing to collect status from 'memory://dvc-staging-md5/cc0bb25b96409904853d0280d6caaf899e5e170c9701e667d661ed11eed04c8e'                                                                                        
2026-09-01 14:00:09,443 DEBUG: link type reflink is not available ([Errno 95] no more link types left to try out)                                                                                                                                  
2026-09-01 14:00:09,443 DEBUG: Removing '/home/joostmeulenbeld/git/mwe_dvc_noignore/output/.svn_L2PF3RMz-pj5F5cSVw.tmp'
2026-09-01 14:00:09,444 DEBUG: Removing '/home/joostmeulenbeld/git/mwe_dvc_noignore/output/.svn_L2PF3RMz-pj5F5cSVw.tmp'
2026-09-01 14:00:09,444 DEBUG: Removing '/home/joostmeulenbeld/git/mwe_dvc_noignore/.dvc/cache/files/md5/.Tsjlf-3HNp_ytWCDNf1QMw.tmp'
2026-09-01 14:00:09,444 DEBUG: Removing '/home/joostmeulenbeld/git/mwe_dvc_noignore/output/out1'                                                                                                                                                   
2026-09-01 14:00:09,467 DEBUG: stage: 'stage1' was reproduced                                                                                                                                                                                      

2026-09-01 14:00:09,475 DEBUG: 'cmd' of stage: 'stage2' has changed.
2026-09-01 14:00:09,475 DEBUG: stage: 'stage2' changed.
2026-09-01 14:00:09,476 DEBUG: 'cmd' of stage: 'stage2' has changed.
2026-09-01 14:00:09,476 DEBUG: stage: 'stage2' changed.
2026-09-01 14:00:09,477 DEBUG: Removing output 'output/out2' of stage: 'stage2'.
2026-09-01 14:00:09,477 DEBUG: Removing '/home/joostmeulenbeld/git/mwe_dvc_noignore/output/out2'
2026-09-01 14:00:09,479 DEBUG: 'cmd' of stage: 'stage2' has changed.                                                                                                                                                                               
Running stage 'stage2':                                                                                                                                                                                                                            
> cp output/out1 output/out2
> exit 1
2026-09-01 14:00:09,501 DEBUG: Removing '/home/joostmeulenbeld/git/mwe_dvc_noignore/output/out1' from gitignore file.
2026-09-01 14:00:09,502 ERROR: failed to reproduce 'stage2': failed to run: exit 1, exited with 1
Traceback (most recent call last):
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/repo/reproduce.py", line 187, in _reproduce
    ret = repro_fn(stage, upstream=upstream, force=force_stage, **kwargs)
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/repo/reproduce.py", line 117, in _reproduce_stage
    ret = stage.reproduce(**kwargs)
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/funcy/decorators.py", line 47, in wrapper
    return deco(call, *dargs, **dkwargs)
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/stage/decorators.py", line 44, in rwlocked
    return call()
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/funcy/decorators.py", line 68, in __call__
    return self._func(*self._args, **self._kwargs)
           ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/stage/__init__.py", line 448, in reproduce
    self.run(**kwargs)
    ~~~~~~~~^^^^^^^^^^
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/funcy/decorators.py", line 47, in wrapper
    return deco(call, *dargs, **dkwargs)
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/stage/decorators.py", line 44, in rwlocked
    return call()
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/funcy/decorators.py", line 68, in __call__
    return self._func(*self._args, **self._kwargs)
           ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/stage/__init__.py", line 601, in run
    self._run_stage(dry, force, **kwargs)
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/funcy/decorators.py", line 47, in wrapper
    return deco(call, *dargs, **dkwargs)
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/stage/decorators.py", line 44, in rwlocked
    return call()
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/funcy/decorators.py", line 68, in __call__
    return self._func(*self._args, **self._kwargs)
           ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/stage/__init__.py", line 628, in _run_stage
    return run_stage(self, dry, force, **kwargs)
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/stage/run.py", line 182, in run_stage
    run(stage, dry=dry, run_env=run_env)
    ~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/stage/decorators.py", line 53, in wrapper
    ret = f(stage, *args, **kwargs)
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/stage/run.py", line 153, in cmd_run
    _run(executable, cmd, **kwargs)
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/stage/run.py", line 133, in _run
    raise StageCmdFailedError(cmd, p.returncode)
dvc.stage.exceptions.StageCmdFailedError: failed to run: exit 1, exited with 1

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/cli/__init__.py", line 212, in main
    ret = cmd.do_run()
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/cli/command.py", line 30, in do_run
    return self.run()
           ~~~~~~~~^^
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/commands/repro.py", line 11, in run
    stages = self.repo.reproduce(**self._common_kwargs, **self._repro_kwargs)
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/repo/__init__.py", line 59, in wrapper
    return f(repo, *args, **kwargs)
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/repo/scm_context.py", line 150, in run
    return method(repo, *args, **kw)
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/repo/reproduce.py", line 248, in reproduce
    return _reproduce(steps, graph=graph, on_error=on_error or "fail", **kwargs)
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/repo/reproduce.py", line 191, in _reproduce
    _raise_error(exc, stage)
    ~~~~~~~~~~~~^^^^^^^^^^^^
  File "/home/joostmeulenbeld/.cache/uv/archive-v0/KgpsAQHnaEG055oR/lib/python3.14/site-packages/dvc/repo/reproduce.py", line 155, in _raise_error
    raise ReproductionError(f"failed to reproduce{segment} {names}") from exc
dvc.exceptions.ReproductionError: failed to reproduce 'stage2'

2026-09-01 14:00:09,506 DEBUG: Analytics is enabled.
2026-09-01 14:00:09,547 DEBUG: Trying to spawn ['daemon', 'analytics', '/tmp/tmpjk5e55cl', '-v']
2026-09-01 14:00:09,555 DEBUG: Spawned ['daemon', 'analytics', '/tmp/tmpjk5e55cl', '-v'] with pid 112440

Activity

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