Skip to content

fix(cli): report a missing pipeline file as bad input, not a crash - #95

Merged
kstonekuan merged 2 commits into
Hebbian-Robotics:mainfrom
chiruu12:fix/up-missing-pipeline-exit-code
Aug 22, 2026
Merged

fix(cli): report a missing pipeline file as bad input, not a crash#95
kstonekuan merged 2 commits into
Hebbian-Robotics:mainfrom
chiruu12:fix/up-missing-pipeline-exit-code

Conversation

@chiruu12

Copy link
Copy Markdown
Contributor

Summary

hflow up --pipeline <missing> printed a traceback and exited 1. It now prints one line and exits 2.

up: [Errno 2] No such file or directory: '/tmp/definitely-missing.py'

Closes #90.

Why

1 and 2 are not interchangeable here. 1 means the runtime started and then failed, so the handler that owns it prints teardown advice and the caller is told containers may still be running. A missing pipeline file fails inside render_bundle, before any container exists, so 1 sent a script after containers that were never created. That is why the new handler is its own except block rather than an addition to the (ComposeError, TimeoutError) tuple.

The raise sites moved to the three-argument FileNotFoundError, matching storage.py:656, so str(error) carries the errno text instead of a bare path. That is four sites: the pipeline and requirements checks in _bundle.py and the same pair in _deploy.py.

deploy needed no CLI change. _command_deploy already caught FileNotFoundError and returned 2, so fixing the raise site fixed its message too. Its old output was deploy: /tmp/x.py, which is the bare-path form #26 was closed for and which only storage.py was ever fixed for.

Two questions a reviewer would reasonably ask, answered up front.

Can the new handler swallow a genuine runtime failure? No. The one plausible source after containers start is a missing docker binary, and _compose.py:49 already converts that into ComposeError. Nothing between compose_up_detached and the return raises FileNotFoundError, so the new handler only ever sees render_bundle.

Does it lie about a path that exists? Yes, in one case, and I left it. is_file() is False for a directory, so hflow up --pipeline <a-directory> now says "No such file or directory" about something that is present. The old bare-path message was not wrong so much as useless, so this is a small regression in accuracy for an input this issue does not cover. Fixing it properly means raising IsADirectoryError, which changes the exception type on a public API and breaks every except FileNotFoundError that currently catches it. That is a design call, and it belongs with the bare-versus-errno question already headed for Discussions rather than in this PR.

Validation

uv run pytest -q                386 passed, 5 failed, 6 skipped
uv run ruff check .             All checks passed!
uv run ruff format --check .    97 files already formatted
uv run ty check                 All checks passed!

The 5 failures are test_ffmpeg.py and test_run_profiles.py on macOS. They reproduce on unmodified ca17730 with no changes applied, so they are the local ffmpeg build and not this change.

Reverting all three source files to origin/main and rerunning the CLI suite:

FAILED test_up_reports_a_missing_pipeline_file_as_bad_input
FAILED test_up_reports_a_missing_requirements_file_the_same_way
FAILED test_deploy_missing_pipeline_names_the_reason_not_just_the_path
FAILED test_deploy_missing_requirements_names_the_reason_too
4 failed, 25 passed

All 29 pass with the change restored. Each of the four raise sites is pinned on its own: reverting any single one of them fails exactly one test.

Behaviour before and after, same command:

before:  traceback, exit 1
after:   up: [Errno 2] No such file or directory: '/tmp/definitely-missing.py'
         exit 2

stderr carries two lines, not one: up: rendering the runtime bundle at <dir> prints first, from the existing progress callback. The definition of done said one line, so flagging the difference. The error itself is one line and the narration is pre-existing behaviour on every up.

Checklist

  • I added or updated outcome-focused tests for changed business logic.
  • I updated documentation for changed behavior, flags, formats, or requirements. No doc change: no documented flag, format or requirement changes, and no doc states the old exit code.
  • I ran uv run ruff check --fix, uv run ruff format, and uv run ty check.
  • I ran the relevant pytest suite.
  • I did not add recordings, generated media, credentials, private URLs, or runtime artifacts.
  • I preserved stored-data compatibility or documented an explicit version change.

Copilot AI lite review requested due to automatic review settings August 22, 2026 09:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@kstonekuan kstonekuan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @chiruu12. Merging.

This is the most thoroughly argued PR the repo has had. Three things stand out.

You fixed the raise sites instead of the symptom. Catching FileNotFoundError in _command_up and printing something nicer would have closed the issue. Moving all four sites to the three-argument form fixed deploy's message as a side effect, with no CLI change, because _command_deploy already caught it and returned 2. That is the smaller diff and the more correct one.

You changed the wording I suggested, and you were right to. The issue proposed up: importing <path> failed: ... to match manifest and stale. Those commands genuinely import the pipeline; up fails in render_bundle before importing anything, so up: [Errno 2] No such file or directory: '<path>' is accurate where my wording would have been a lie about what the command was doing. I did not notice that when I wrote the issue.

You answered the two questions a reviewer would ask, before being asked. I went to check whether the new handler could swallow a genuine post-start failure and found you had already traced it: _compose.py:49 converts a missing docker into ComposeError, so the handler only ever sees render_bundle. That saved me the trip.

The directory case is the right call. --pipeline <a-directory> now says "No such file or directory" about something that exists, and the honest fix is IsADirectoryError, which changes the exception type on a public API and breaks every existing except FileNotFoundError. Deferring that to the Discussions thread with the bare-versus-errno question is correct. Flagging it in the PR body rather than leaving me to find it is what made it a non-issue.

Same for the two-line stderr: the definition of done said one line, the narration is pre-existing on every up, and saying so up front is better than silently not matching.

What I validated on your branch:

  • ruff check, ruff format --check, ty check clean; 394 passed, 3 skipped. Your branch was already on current main (ca17730).
  • hflow up --pipeline <missing>: one error line, exit 2, no traceback. hflow deploy likewise exits 2 and now prints the errno text instead of the bare path.
  • Your revert-each-site claim holds: each of the four raise sites is pinned by exactly one test.

The #26 observation was a good catch and worth stating plainly: that issue was closed with only storage.py fixed, and the pattern it was closed for survived in four other places. That is the kind of thing worth reopening an old issue over.

Open the Discussions thread whenever you want. Two questions are now queued for it: whether the bare FileNotFoundError(path) construction should be centralized, and whether a directory should raise IsADirectoryError. Both are real, and the second one is a public-API decision, so it should not be settled in a review comment.

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.

CLI: hflow up prints a traceback for a missing pipeline file

3 participants