Always surface the Nix eval error, never a stale warning#2849
Always surface the Nix eval error, never a stale warning#2849domenkozar wants to merge 5 commits into
Conversation
Deploying devenv with
|
| Latest commit: |
c0174f8
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://1fe5a2b9.devenv.pages.dev |
| Branch Preview URL: | https://fix-2820-actionable-error.devenv.pages.dev |
e063888 to
d02361d
Compare
🔍 Suggested ReviewersBased on git blame analysis of the changed lines, the following contributors have significant experience with the modified code:
Please consider reviewing this PR as you have authored significant portions of the code being modified. Your expertise would be valuable! 🙏 This comment was automatically generated by git-blame-auto-reviewer Last updated: 2026-05-29T22:29:44.550Z |
28d78b9 to
4115710
Compare
| - Fixed import path validation rejecting valid input-relative imports on macOS, where `TMPDIR` resolves via the `/var → /private/var` symlink (`Import path '<name>/...' resolves outside the git repository`). `validate_within_root` now canonicalizes the longest existing ancestor of the path before comparing against the root. | ||
| - Fixed `devenv inputs add` from a subdirectory writing to a stray `devenv.yaml` in the subdir instead of the enclosing project. It now walks up to find `devenv.nix` the same way `devenv shell` does, so the input is added where the rest of devenv reads it. |
There was a problem hiding this comment.
Looks like some other project dir fixes got bundled in
There was a problem hiding this comment.
I split them into separate PRs
| /// `devenv.nix` so the command operates on the enclosing project, matching | ||
| /// where `devenv shell` would run. Best-effort — a no-op if discovery or the | ||
| /// chdir fails. | ||
| fn enter_discovered_project_root() { |
There was a problem hiding this comment.
Worth adding some traces here for the failure modes.
I'd also think about whether we might want to resolve the project root earlier in the flow. Maybe devenv allow also wants the project root? And do we even want to chdir at all?
There was a problem hiding this comment.
Added debug/warn traces for the three failure modes (unreadable cwd, no enclosing devenv.nix, and a found-but-failed chdir, which is the one that actually leaves inputs add writing to the wrong place). Since this devenv inputs add change is unrelated to error surfacing, I moved it to #2883 — the traces live there now.
On the design questions: agreed that resolving the project root once, earlier in the flow, would be cleaner than the two ad-hoc discovery sites, and devenv allow probably wants it too (incl. whether we should chdir at all vs. threading the root through). I'd rather not expand the split-out PR's scope, so I captured it as a follow-up note on #2883.
A Nix eval error with `show-trace=true` (always on in devenv) rendered as a single line headline `Failed to get shell attribute from devenv:` followed by ~120 lines of `lib/modules.nix` frames, with the actionable text (the thrown assertion, e.g. `$ devenv inputs add git-hooks ...`) buried at the very bottom. The follow up comments on #2820 reported the suggestion as "swallowed". `format_eval_error` now extracts the trailing `error: ...` paragraph from the FFI return value and uses it as the diagnostic headline; the preceding frames move to miette's `help:` section. When no `error:` paragraph is present (malformed input, future format change), the original text is rendered as one block (graceful fallback). Vestigial code removed: `select_raw_error`, `looks_like_warning`, `peek_pre_repl_errors`. The C bindings now return the full error via FFI rather than the log callback, so the log buffer merge no longer contributes anything. `take_pre_repl_errors` stays because `launch_repl` uses it to replay TUI swallowed log lines. Tests: * 5 new unit tests on `format_eval_error` covering the happy path, the bare syntax error shape, warning shadow regression, graceful degradation, and indent stripping (no FFI required, all under 40ms). * `tests/missing-input-error/` new integration test asserting the `devenv inputs add ...` suggestion appears alongside the headline. * `tests/syntax-error/.test.sh` gains a warning shadow assertion on the headline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`split_trailing_error` matched any line whose stripped prefix was
`error:`. That misfired on user-authored multi-line `throw`s whose
message body contained a line like `error: my note`: my parser picked
the user's literal text as the boundary, leaving the real Nix `error:`
keyword and the leading lines of the throw in the demoted trace.
Reproduction:
env.GREET = throw ''
Step 1: do this
error: but this is the actual problem
Step 3: then this
'';
Before: headline `× ... error: but this is the actual problem` plus
`Step 3: then this`, with `Step 1: do this` buried in `help:`.
After: headline starts with Nix's real `error:` keyword and carries the
full user throw body.
Discriminator: Nix's real `error:` paragraphs are always separated from
preceding content by at least one blank line. User content inside a
`throw` sits on consecutive lines. Require the boundary line to be
preceded by a blank line (or start of input).
Tests:
* New `split_trailing_error_ignores_error_keyword_inside_user_throw_body`
locks in the regression.
* `split_trailing_error_picks_last_error_when_multiple_present` and
`split_trailing_error_handles_ansi_prefixed_error_line` get an
explicit blank line in their fixtures so they model what Nix actually
emits.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With no leading trace and 4+ `error:` paragraphs, the early-return guard fired at the third paragraph and leaked later paragraphs into the headline. Match on `error_start` instead so "last error wins" actually holds for the no-leading-trace case while still cutting at the first boundary when leading trace is present. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`split_trailing_error` used an empty `&str` to signal "no leading
trace", forcing callers to `is_empty()`-check a value that otherwise
reads like real text. Return `Option<&str>` so the absence of a trace
lives in the type and `format_eval_error` matches on it directly.
A trace that trims down to empty (input opens with `error:`, or only
blank lines precede the first `error:`) now maps to `None` rather than
`Some("")`, via a small `split_at` helper shared by both exit paths.
No behaviour change for callers.
Addresses review feedback on #2849.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
158484f to
d4770c2
Compare
`validate_within_root` rejected valid input-relative imports on macOS, where `TMPDIR` resolves via the `/var -> /private/var` symlink and the import path's leaf does not exist yet (`Import path '<name>/...' resolves outside the git repository`). Resolve the longest existing ancestor with `canonicalize` and append the remaining components lexically, so symlinks anywhere up the chain are followed even when the leaf is absent. Split out of #2849. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`Inputs::Add` is dispatched before `resolve()`, so the parent-directory discovery that `devenv shell` benefits from never ran for it — running `devenv inputs add` from a subdirectory silently created a stray `devenv.yaml` in the subdir that no other command would read. Apply the same chdir-up-to-`devenv.nix` step in the dispatch arm so the input lands in the enclosing project's config, and trace the discovery's failure modes so the best-effort behaviour is observable. Also compare against the physical path in the cli-subdir-discovery test so it passes on macOS, where `TMPDIR` resolves via the `/var -> /private/var` symlink. Split out of #2849. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`Inputs::Add` is dispatched before `resolve()`, so the parent-directory discovery that `devenv shell` benefits from never ran for it — running `devenv inputs add` from a subdirectory silently created a stray `devenv.yaml` in the subdir that no other command would read. Apply the same chdir-up-to-`devenv.nix` step in the dispatch arm so the input lands in the enclosing project's config, and trace the discovery's failure modes so the best-effort behaviour is observable. Also compare against the physical path in the cli-subdir-discovery test so it passes on macOS, where `TMPDIR` resolves via the `/var -> /private/var` symlink. Split out of #2849. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`Inputs::Add` is dispatched before `resolve()`, so the parent-directory discovery that `devenv shell` benefits from never ran for it — running `devenv inputs add` from a subdirectory silently created a stray `devenv.yaml` in the subdir that no other command would read. Apply the same chdir-up-to-`devenv.nix` step in the dispatch arm so the input lands in the enclosing project's config, and trace the discovery's failure modes so the best-effort behaviour is observable. Also compare against the physical path in the cli-subdir-discovery test so it passes on macOS, where `TMPDIR` resolves via the `/var -> /private/var` symlink. Split out of cachix#2849. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
I've figured out the cause for #2820. The color of the warning in the logs changed, causing We need to:
As for this PR, it currently prints the suggestion above the trace, requiring users to scroll up to find it. What we had before was better. |
Review feedback on #2849: placing the actionable error paragraph above the --show-trace frames forced users to scroll up past ~120 lines to find it. Render the eval-returned error as-is (modulo dedenting) so the actionable message stays at the bottom where the cursor lands. The actual fix for #2820 is unchanged: always render the error returned by eval instead of a log line captured during evaluation, so a stale warning arriving at error verbosity can never shadow the real error. This drops split_trailing_error and the headline/help reshaping, which no longer have a purpose. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Addressed in c0174f8:
The integration tests now assert the suggestion appears below the trace frames and that the stale warning never shadows the real error. |
Summary
Fixes #2820: when Nix emitted a warning at error verbosity (e.g.
Ignoring the client-specified setting 'system'for untrusted users), devenv selected that log line as the error message and dropped the real error returned by eval. Syntax errors and actionable suggestions likedevenv inputs add …were hidden behind the warning.Root cause (thanks @sandydoo): the warning's color changed,
looks_like_warningstopped matching, and the fallback treated the warning line as the main error.What changed
--show-traceframes first, the actionableerror: …paragraph last, at the bottom of the terminal where the cursor lands.select_raw_error,looks_like_warning, andpeek_pre_repl_errorsare gone. There is no path left where a captured log line replaces the eval error, and no color matching that can rot.An earlier revision of this PR reordered the output to put the actionable paragraph above the trace; that was reverted per review since it forced scrolling up past the trace to find it.
Tests
format_eval_error: eval error always rendered (warning-shadow regression), natural trace-then-error order preserved, bare syntax error, no-error:-line fallback, dedenting.tests/missing-input-error/integration test: thedevenv inputs addsuggestion is printed below the trace frames, and the headline never carries the stale warning.tests/syntax-error/.test.shgains an assertion that the warning does not shadow the syntax error.Test plan
cargo nextest run -p devenv-nix-backend error::devenv-run-tests run --only syntax-error --only missing-input-error tests🤖 Generated with Claude Code