fix(cli): route rocm fix failures to stderr, preserve log guard on exit - #350
Conversation
bbb1b02 to
aa563d8
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Generic error reporting can panic on stderr failure, and an important command-failure path lacks scenario coverage.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Routes rocm fix failure diagnostics to stderr while preserving log cleanup during exit.
Changes:
- Moves fix failure messages from stdout to stderr.
- Propagates fix exit codes through
main(). - Updates unit and end-to-end assertions.
File summaries
| File | Description |
|---|---|
apps/rocm/src/main.rs |
Adds structured exit-code handling. |
crates/rocm-core/src/fix.rs |
Routes failure diagnostics to stderr. |
tests/e2e-cucumber/tests/e2e/diagnose_steps.rs |
Verifies selected stderr diagnostics. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 2
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
The new unit test unsafely mutates global environment state, and several comments incorrectly describe anyhow downcasting behavior.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
apps/rocm/src/main.rs:1714
- This repeats the incorrect claim that adding
anyhow::Contextbreaks the downcast.anyhowdeliberately preserves downcasting to the original error through context layers, so this prohibition should be removed or replaced with an accurate description of the carried marker.
apps/rocm/src/main.rs:19174 - The test does exercise the dispatch chain, but it does not guard against adding
.context(...):anyhowpreservesFixExitCodedowncasts through context layers, so that proposed change would keep this test passing. Update the comment to describe what the test actually guarantees.
- Files reviewed: 4/4 changed files
- Comments generated: 2
- Review effort level: Balanced
Failure explanations in `rocm fix` (declined confirmation, wrong-OS refusal, subprocess failures) were printed to stdout despite the command exiting non-zero, so a caller capturing only stdout on success sees a clean-looking failure with no visible reason. Separately, `rocm fix` was the only place in the codebase calling std::process::exit directly, which skips main()'s _log_guard destructor and can silently truncate the log on exactly the failure paths where it matters most. Its exit code now flows back through main()'s ordinary return via a FixExitCode marker error instead. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
run_path_export_linux/windows had four failure-explanation println! calls left over when this branch's other rocm-fix failure messages moved to stderr; they return the same exit code (3) as sibling lines in the same functions that were converted, so leaving them on stdout broke the stream/exit-code consistency this change established. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
assert_inapplicable_fix_declined and assert_refuses_without_agreement checked stdout+stderr concatenated, so they would still pass if the refusal text regressed back onto stdout -- the one thing this PR's stderr move needs to guarantee. Assert against cli_stderr alone. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
eprintln! panics on a write failure (e.g. closed stderr pipe), which would replace the intended exit code with a panic. Match std's Termination behavior for Result<(), E> and ignore the write failure instead. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
diagnose-08/-11 cover the refusal branches (no agreement, wrong OS), but the third failure shape -- an approved, applicable fix whose helper command itself fails -- had no e2e coverage. Add diagnose-13, which forces usermod/sudo to fail via a scenario-scoped PATH override and asserts the explanation lands on stderr with exit code 4. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
assert_unknown_fix_refused and assert_position_argument_corrected checked stdout+stderr concatenated, the same weakness already fixed for their sibling assertions in this branch's stderr migration. Both messages are eprintln!-only already, so tighten these two the same way for consistency. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
exit_code_for recovers the exit code via downcast_ref, which only works if FixExitCode reaches it unwrapped. Note the invariant so a future .context() on the fix() call doesn't silently break it. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
… errors - confirm() now prints "Not confirmed; refusing to apply." on every decline path (interactive no/EOF, not just the non-interactive refusal), and its yes/no parsing is extracted into is_affirmative_answer() with direct unit tests. - Replace fix.rs's failure-explanation eprintln! calls with a fail! macro that ignores write errors, matching the panic-safe pattern main.rs already uses for its generic error fallback. - Add a regression test exercising the real dispatch -> fix -> FixExitCode -> exit_code_for chain, plus a pointer comment at the Fix dispatch arm warning against wrapping the call (e.g. with .context(...)), which would silently break the exit-code downcast. The interactive decline path was manually verified via a real PTY (prints the message, exits 5); no new PTY e2e scenario was added since it would require extending the TUI-specific PTY driver for a case already covered by the unit test and the shared confirm() fix. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
…w test Two review comments from earlier in the PR were already resolved by prior commits (exit_code_for's panic-safe write, e2e coverage for the stderr command-failure branch). Two were real: - The doc comment claiming .context(...) breaks the FixExitCode downcast was wrong: anyhow::Error::downcast_ref searches the whole error chain, so context-wrapping is safe. What actually breaks the downcast is discarding the error into a fresh anyhow!(...) instead of chaining it. Corrected the claim at all three sites (the FixExitCode doc comment, the dispatch-arm pointer comment, and the regression test's doc comment). - The new dispatch-level regression test mutated ROCM_CLI_DISABLE_STARTUP_UPDATE_CHECK via raw env::set_var/remove_var instead of the existing ScopedTestEnv guard, so it wasn't serialized against other env-touching tests and would leak the mutation on a panic. Switched to ScopedTestEnv. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
d4d1127 to
0cef54d
Compare
|
🔴 Automated review · pr-review-watcher · 0cef54d SummaryRoutes 🚫 Blocking (must fix before merge)None. Non-blocking
|
…teractive decline Closes the remaining non-blocking findings from PR #350's review (#350 (comment)), all in the same class of bug that PR fixed for `fix()`: - `parse_cli()` and the mistyped-subcommand branch in `run()` still called `clap::Error::exit()` directly, bypassing `_log_guard`'s destructor the same way the pre-PR `fix()` did. Both now return a `ClapExitCode` marker error through the ordinary `Result` path instead. - Four call sites relaying a captured subprocess's stdout/stderr with `print!`/`eprint!` could still panic on a closed pipe, unlike `fail!`. Consolidated them behind a `relay_output` helper that ignores write failures for the same reason `fail!` does. - `confirm()`'s interactive-decline branch had no e2e coverage: the piped- stdin harness can never reach it, since `is_terminal()` is always false there. Added a PTY-driven scenario (diagnose-14) that types "n" at a real terminal prompt and asserts the same refusal outcome as the non-interactive path. - Strengthened the diagnose-08/-11 refusal assertions to also check the message never leaks onto stdout, matching the bar diagnose-13 already set. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
|
Addressed all 5 non-blocking findings from the review in 294a183:
Verified: |
|
🔴 Automated review · pr-review-watcher · 294a183 SummaryRoutes 🚫 Blocking (must fix before merge)None. Non-blocking
|
siloteemu
left a comment
There was a problem hiding this comment.
Reviewed at 294a1832: no blocking issues. Routing the failure and refusal messages to the error stream, and returning the exit code through main() so the logging guard's destructor runs, both check out — and the new scenario drives a real terminal rather than a pipe, so it reaches the interactive-decline branch the piped harness cannot.
Worth noting for the record: this is the one change in a 35-PR sweep that removed a non-discriminating test shape rather than adding one — replacing "does the combined output contain this message" with an assertion that the text is on the error stream and absent from standard output.
Five non-blocking notes are in the review comment. The one worth acting on before or after merge: the common usage error path still bypasses the guard, so the commit subject claims broader coverage than the change delivers.
Renumber the branch's new diagnose scenarios to 15/16; main independently added its own 13/14 (vLLM import recognition, multi-shell plan). Signed-off-by: Jussi Elo <jussi.elo@amd.com>
…path parse_cli() called clap's get_matches(), which self-exits on a parse error and skips the log guard's flush -- the same bug this PR already fixed for fix(). Switch to try_get_matches() and share the print+wrap logic with the mistyped-subcommand branch via a new clap_exit_code helper, and rewrite the round-trip test to exercise that helper on a real clap::Error instead of a hand-built marker. Also give TuiSession callers a way to override HOME/SHELL the way the piped run_rocm_with_env path already can, and use it in diagnose-16 so the interactive-decline scenario points at the same rc file the Given step planted -- plus assert that file stays untouched, matching its piped sibling. Signed-off-by: Jussi Elo <jussi.elo@amd.com>
|
Addressed the non-blocking findings from the latest review round (8649dff):
Verified with |
Summary
rocm fixfailure explanations ("refusing to apply", "usermod exited N; group membership NOT changed", etc.) now go to stderr instead of stdout, so a caller capturing only stdout on a non-zero exit still sees why.rocm fixno longer callsstd::process::exitdirectly — it was the only place in the codebase doing so, and it skippedmain()'s_log_guarddestructor, risking a truncated log on exactly the failure paths where it matters most. Its exit code now flows back throughmain()'s ordinary return via aFixExitCodemarker error, downcast in a smallexit_code_for()helper.Test plan
cargo test -p rocm-core fix— 23 passedcargo test -p rocm— 555 passed (+3 new unit tests forexit_code_for)cargo xtask e2e -- -n "diagnose-0[5-8]|diagnose-11|diagnose-12"— all 6 fix-related e2e scenarios pass, including the two updated stderr assertionscargo fmt --checkandcargo clippy --workspace --all-targets -- -D warningscleangrep -rn "process::exit" apps/rocm/src crates/rocm-core/src— no remaining rawstd::process::exitcalls (the pre-existing claperr.exit()for mistyped invocations is untouched, flagged as a separate out-of-scope follow-up)