Skip to content

fix: keep watching when a file in the tree fails to compile - #6313

Merged
max-sixty merged 5 commits into
mainfrom
fix/watch-continues-past-compile-errors
Sep 15, 2026
Merged

max-sixty merged 5 commits into
mainfrom
fix/watch-continues-past-compile-errors

Conversation

@prql-bot

@prql-bot prql-bot commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

prqlc watch <dir> exited with status 1 instead of watching, whenever any .prql file under the watched tree failed to compile — which is the ordinary starting state for watch mode, and the one it exists to iterate out of.

$ ls watched/
bad.prql  good.prql
$ prqlc watch watched/
Compiling watched/good.prql
Compiling watched/bad.prql
Error:
   ╭─[ :1:15 ]

 1 │ from tracks | filter
   │               ───┬──
   │                  ╰──── main expected type `relation`, but found type `func relation -> relation`
───╯

failed to compile
$ echo $?
1

Note that "Watching path" is never printed: the initial pass in find_and_compile propagated the first compile error out through run, so the command returned before watch_and_compile was ever called. Fixing the file does nothing, because nothing is watching it.

The watch loop itself has always kept going past a file that fails — but by discarding the error outright (let _ignore = compile_path(...)). compile_path prints the compiler's own diagnostics before returning, so a compile failure was still visible there; the errors it returns without printing were not. An unwritable .sql output path, or a Jinja pre-processing failure, left the watcher running and producing nothing, with no indication why.

Both call sites now go through one compile_path_reporting_errors, which prints <path>: <error> and carries on, so the walk still continues past every failure but nothing disappears:

$ chmod 555 watched/    # read-only output dir
$ prqlc watch watched/
Compiling watched/good.prql
watched/good.prql: Permission denied (os error 13)
Compiling watched/bad.prql
Error:
   ╭─[ :1:15 ]

 1 │ from tracks | filter
   │               ───┬──
   │                  ╰──── main expected type `relation`, but found type `func relation -> relation`
───╯

watched/bad.prql: failed to compile
Watching path "watched/"

The path prefix also supplies the file name the compiler's diagnostic omits — note the empty span header above.

Errors from the directory walk itself (entry?) still abort, unchanged — an unreadable tree is not something watching can recover from.

The regression test calls find_and_compile directly over a temp dir holding two good files and one broken one, and asserts the two .sql outputs exist and the walk returned Ok. It fails on the parent commit with called `Result::unwrap()` on an `Err` value: failed to compile. An end-to-end test of the watch subcommand isn't practical in cli/test.rs, since the command blocks forever by design.

The initial pass in `find_and_compile` propagated a compile error, so
`prqlc watch` exited before reaching the watch loop whenever any .prql
file in the tree was broken — the state watch mode exists to iterate out
of. Report and continue instead, matching what the watch loop already
does for errors that appear after startup.

@prql-bot prql-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Self-review. The change does what it says — the initial pass no longer aborts, and the watcher comes up — but let _ignore = compile_path(...) drops more than compile errors, and the non-compile ones have nothing else printing them.

compile_path returns Err from three places: the compile-error branch, which prints the compiler's diagnostics itself before returning anyhow!("failed to compile"); jinja::pre_process(...)?; and fs::write(sql_path, ...)?. Only the first is self-reporting. So on a watched tree whose .sql files can't be written, the pre-change command exited 1 with Permission denied (os error 13), and the post-change one printed Compiling <path>, wrote nothing, said nothing, and went on to watch forever:

$ chmod 555 watched/    # read-only output dir
$ prqlc watch watched/
Compiling watched/good.prql
Watching path "watched/"

That's a worse failure than the one being fixed: the user gets no error and no output, and no amount of editing changes either. The watch loop has the same let _ignore, so the same write failure after startup is equally silent — parity with it is the PR's own argument, so fixing one path and not the other just relocates the inconsistency.

Pushing the fix: both call sites go through one compile_path_reporting_errors helper that prints the error instead of discarding it, so the walk still continues past every failure but nothing disappears.

$ prqlc watch watched/
Compiling watched/good.prql
watched/good.prql: Permission denied (os error 13)
Compiling watched/bad.prql
Error:
   ╭─[ :1:15 ]
...
watched/bad.prql: failed to compile
Watching path "watched/"

Also in the push: the test comment claimed the file names put the failing file ahead of the second good one, but WalkDir isn't constructed with sort_by_file_name, so entry order is whatever read_dir returns. The test is a valid guard in any order — the bad file is always walked, so the parent commit's ? panics the unwrap regardless — but the comment asserted a guarantee that isn't there. Reworded to say why there are two good files rather than claiming an order.

`let _ignore = compile_path(...)` dropped every error, but only the
compile-error branch prints anything of its own — a failed `.sql` write
or a Jinja pre-process error went entirely silent, so a read-only output
directory left the watcher running and producing nothing.

Both call sites now go through one helper that prints the error instead
of discarding it, keeping the walk going past every failure without
losing the failures themselves.

@prql-bot prql-bot left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Self-review of the reporting commit. The behavior checks out — running the built binary against a read-only output directory prints watched/good.prql: Permission denied (os error 13), then the compile diagnostic for the broken file, then Watching path — but the CHANGELOG entry and the PR description still describe only the first half of the PR.

The entry says the initial pass now behaves "as it already did for errors that appear after it starts". The loop did keep watching, but it discarded every error (let _ignore = compile_path(...)), so a failure that produces no compiler diagnostic — an unwritable .sql output path, a Jinja pre-processing failure — was silent there too. Ending that silence is user-facing in its own right, and the contributing guide asks for a line per user-facing change ("If a change is user-facing, please add a line in CHANGELOG.md", web/book/src/project/contributing/development.md under Commits), so it belongs in the entry rather than being implied away by the initial-pass wording.

Pushing the reworded entry and a description that covers both halves; no code change.

@max-sixty
max-sixty merged commit 24f6a8f into main Sep 15, 2026
36 checks passed
@max-sixty
max-sixty deleted the fix/watch-continues-past-compile-errors branch September 15, 2026 08:56
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.

2 participants