Skip to content

fix(push): report a bad target instead of a traceback (0.25.1) - #187

Merged
johnyaku merged 1 commit into
mainfrom
fix/push-bad-target
Aug 25, 2026
Merged

johnyaku merged 1 commit into
mainfrom
fix/push-bad-target

Conversation

@johnyaku

@johnyaku johnyaku commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

dt push myremote -- the -r left off -- read the remote name as a target and died inside DVC's collection with a traceback. The diagnosis was in there, but you had to read it out of a stack.

Before:

$ dt push --dry myremote
Traceback (most recent call last):
  ...
  File ".../dvc/repo/stage.py", line 412, in collect_granular
    raise NoOutputOrStageError(target, exc.file) from exc
dvc.exceptions.NoOutputOrStageError: 'myremote' does not exist as an output or a stage name in 'dvc.yaml'

After, in every mode -- plain, --dry, --workers:

$ dt push myremote
Error: No such target: 'myremote' (not a tracked path, a .dvc file, or a stage name)

'myremote' is a configured remote, not a target. To push to it:
    dt push -r myremote

Where the fix sits

The traceback is fixed once, in utils.collect_tracked_entries. That helper is the single place we call DVC's _collect_indexes directly, so catching DVC's target-resolution exceptions there (NoOutputOrStageError, OutputNotFoundError, PathMissingError, StageFileDoesNotExistError, StageNotFound) and re-raising TargetNotFoundError covers dt cache too, not just push. The new exception carries the offending targets so each caller can add its own hint.

The hint is push's, because only push knows about -r. explain_bad_target() checks the target against the configured remote names and suggests -r NAME when it matches; otherwise it just says what a target is.

The plain path needed a different mechanism. Without --workers, dt push shells out to dvc push, so there is no exception of ours to catch -- only DVC's stderr. Rather than pattern-match that output, check_targets() runs first. It refuses a target only when it both cannot resolve and names a configured remote, which keeps it from turning away anything DVC would have accepted.

Why the resolvability check is permissive

unresolvable_targets() accepts more than "a path that exists": a .dvc file, a stage name in dvc.yaml, dvc.yaml:stage, or a tracked parent directory all count.

That last one is not theoretical. Checked against real DVC:

$ rm dir/b.txt          # still in the cache, just not checked out
$ dvc push --dry dir/b.txt
# succeeds -- resolved through dir.dvc

A stricter "does this path exist" test would have rejected a valid push. The cost of erring this way is that a mistyped non-remote target in plain dt push still gets DVC's own message rather than ours -- no traceback either way, and DVC's message is clear.

Also fixed

Target extraction was [arg for arg in ctx.args if not arg.startswith('-')], which takes an option's value for a target -- dt push --dry --jobs 8 failed on target 8. extract_targets() now skips the value of a value-taking option.

Tests

New coverage for the conversion in utils, the resolvability rules (including the not-checked-out cases above), extract_targets, explain_bad_target, and check_targets. Full unit suite on a compute node: 2408 passed, 2 skipped.

🤖 Generated with Claude Code

`dt push myremote` -- the `-r` left off -- read the remote name as a target
and died in DVC's collection with a traceback. The diagnosis was in there,
but you had to read it out of a stack.

Two parts. `utils.collect_tracked_entries` now catches DVC's
target-resolution exceptions and raises `TargetNotFoundError` naming the
target, which covers every caller of that helper, not just push. On top of
that, push says what the mistake probably was: if the target is a
configured remote name, it suggests `-r`.

The plain push path shells out to `dvc push`, so it gets a pre-flight check
rather than an exception to catch. That check refuses a target only when it
both cannot resolve and names a remote, so nothing DVC would have accepted
is turned away.

Also fixes target extraction, which treated an option's value as a target:
`dt push --dry --jobs 8` used to fail on target `8`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@johnyaku
johnyaku merged commit b802a00 into main Aug 25, 2026
1 check passed
@johnyaku
johnyaku deleted the fix/push-bad-target branch August 25, 2026 03:59
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.

1 participant