Skip to content

Error on non-zero exit statuses#13515

Merged
IanManske merged 21 commits intonushell:mainfrom
IanManske:exit-code-error
Sep 7, 2024
Merged

Error on non-zero exit statuses#13515
IanManske merged 21 commits intonushell:mainfrom
IanManske:exit-code-error

Conversation

@IanManske
Copy link
Copy Markdown
Member

@IanManske IanManske commented Aug 2, 2024

Description

This PR makes it so that non-zero exit codes and termination by signal are treated as a normal ShellError. Currently, these are silent errors. That is, if an external command fails, then it's code block is aborted, but the parent block can sometimes continue execution. E.g., see #8569 and this example:

[1 2] | each { ^false }

Before this would give:

╭───┬──╮
│ 0 │  │
│ 1 │  │
╰───┴──╯

Now, this shows an error:

Error: nu::shell::eval_block_with_input

  × Eval block failed with pipeline input
   ╭─[entry #1:1:2]
 1 │ [1 2] | each { ^false }
   ·  ┬
   ·  ╰── source value
   ╰────

Error: nu::shell::non_zero_exit_code

  × External command had a non-zero exit code
   ╭─[entry #1:1:17]
 1 │ [1 2] | each { ^false }
   ·                 ──┬──
   ·                   ╰── exited with code 1
   ╰────

This PR fixes #12874, fixes #5960, fixes #10856, and fixes #5347. This PR also partially addresses #10633 and #10624 (only the last command of a pipeline is currently checked). It looks like #8569 is already fixed, but this PR will make sure it is definitely fixed (fixes #8569).

User-Facing Changes

  • Non-zero exit codes and termination by signal now cause an error to be thrown.
  • The error record value passed to a catch block may now have an exit_code column containing the integer exit code if the error was due to an external command.
  • Adds new config values, display_errors.exit_code and display_errors.termination_signal, which determine whether an error message should be printed in the respective error cases. For non-interactive sessions, these are set to true, and for interactive sessions display_errors.exit_code is false (via the default config).

Tests

Added a few tests.

After Submitting

  • Update docs and book.
  • Future work:
    • Error if other external commands besides the last in a pipeline exit with a non-zero exit code. Then, deprecate do -c since this will be the default behavior everywhere.
    • Add a better mechanism for exit codes and deprecate $env.LAST_EXIT_CODE (it's buggy).

@IanManske IanManske added notes:breaking-changes This PR implies a change affecting users and has to be noted in the release notes notes:mention Include the release notes summary in the "Hall of Fame" section deprecated:pr-errors Deprecated: use A:error-handling, A:error-unhelpful, or A:error-silent-fail instead deprecated:pr-language (deprecated: this label is too vague) This PR makes some language changes labels Aug 2, 2024
@kubouch
Copy link
Copy Markdown
Contributor

kubouch commented Aug 8, 2024

The problem with this is that programs often exit with a non-zero exit code even when they exit successfully. Some programs expect to be terminated by Ctrl-C. I think throwing errors in all these cases might produce too much noise.

We used to have a config setting for throwing errors on non-zero exit code, but I kept it off because it was too annoying.

@Hofer-Julian
Copy link
Copy Markdown
Contributor

Hofer-Julian commented Aug 28, 2024

We used to have a config setting for throwing errors on non-zero exit code, but I kept it off because it was too annoying.

I just stumbled across this PR, and I'd be curious what exactly made this annoying.
I assume the problem is that a ShellError would be reported like the image below, which would be annoying in the interactive use case as described in Sophie's post.

image

I think for exit codes > 0, you want to stop execution of a script, but don´t want a scary/annoying error message for your interactive use case every time you cancel git log.

I think the best way to get out of this is do define a specific Error which is only used when external commands return with an exit code > 0. That Error will not be reported in the interactive shell. In the script, nothing needs to change, it's easy to ignore errors with do

@kubouch
Copy link
Copy Markdown
Contributor

kubouch commented Aug 28, 2024

I don't remember the details, but the Sophia's article highlights the point well. E.g., after running git diff of a longer diff, you'd get a shell error reported in the REPL, that's annoying. There are also some tools that when you run them without any args, they print the help message to stderr and non-zero exit code (for example git diff outside a git repository). While technically this could be interpreted as an error, sometimes I run commands like that just to show the help message, and the error would be annoying.

Although, now it seems that code stops executing when encountering a non-zero exit code, in which case you probably do want to have an error, so the suggestion of having a separate Error for this case that gets ignored in REPL seems reasonable.

@Hofer-Julian
Copy link
Copy Markdown
Contributor

so the suggestion of having a separate Error for this case that gets ignored in REPL seems reasonable.

@IanManske is this something you'd be interested in working on?

@IanManske
Copy link
Copy Markdown
Member Author

so the suggestion of having a separate Error for this case that gets ignored in REPL seems reasonable.

@IanManske is this something you'd be interested in working on?

Yes, this is what I was thinking as well. I think it should not show by default in the REPL, but I would still like it to be configurable. So, I was wondering how to best structure the config, but I think I have a decent idea now. I'll have time on the weekend to hopefully knock this PR out.

@kubouch
Copy link
Copy Markdown
Contributor

kubouch commented Aug 29, 2024

Making the error showing / not showing in a REPL configurable sounds OK to me, but I wouldn't make stopping the execution in a script configurable because semantics shouldn't depend on the config. This might not be honored in 100% of the cases currently, but IMO we should strive to keep Nushell's execution as deterministic as possible.

@sholderbach
Copy link
Copy Markdown
Member

One thing to consider is whether we want different behavior for the trailing pipeline position vs. for a pipeline element? Is the assumption correct that when you want to pipe things you want to probably handle the exit code vs at the last part of a statement where it may be a result in of itself?

@Hofer-Julian
Copy link
Copy Markdown
Contributor

@sholderbach Maybe others in this thread feel differently, but I struggle a bit to understand the situation you are describing. Could you maybe give one or two examples with code? :)

@IanManske
Copy link
Copy Markdown
Member Author

One thing to consider is whether we want different behavior for the trailing pipeline position vs. for a pipeline element?

Our current code structure makes it impossible to check the exit codes of pipeline elements besides the last, so this PR only checks the trailing element's exit code. One exception is if a command has to collect it's input, and the previous command was an external command. In that case, the exit code is checked as well.

@sholderbach
Copy link
Copy Markdown
Member

Could you maybe give one or two examples with code? :)

My gut feeling was that when comparing a) or b)

# a)
foo | may-fail
# b)
foo | may-fail | bar

that the example b) encodes more of an expectation that things are designed to work together explicitly, so an unhandled exit code presents a true error even in the repl. While a) may present an error in the context of a script the semantics are less clear cut for the repl.

But as @IanManske mentioned that is not the primary focus of the current logic

Our current code structure makes it impossible to check the exit codes of pipeline elements besides the last

In that sense for b) the handling is inverse that only erroring when explicitly asked for through collect.

One exception is if a command has to collect it's input, and the previous command was an external command. In that case, the exit code is checked as well.

@Hofer-Julian
Copy link
Copy Markdown
Contributor

Thanks for the explanation @sholderbach

In that sense for b) the handling is inverse that only erroring when explicitly asked for through collect.

That is a bit unfortunate. At least for me that will mean that many errors will go unnoticed. But nothing that can be done about this in the short term I guess.

@fdncred
Copy link
Copy Markdown
Contributor

fdncred commented Sep 4, 2024

How does this PR work on non-zero exit codes that are not errors like mentioned here https://www.sophiajt.com/exit-codes/?

@IanManske
Copy link
Copy Markdown
Member Author

IanManske commented Sep 5, 2024

This PR makes it so that nonzero exit codes are treated as errors, so an error would be triggered in that example. In interactive mode / the REPL, no error message will be displayed by default though. If a non-zero exit code needs to be ignored/caught in, e.g., a script, then complete or try can be used (this is the same as in nushell today).

@fdncred
Copy link
Copy Markdown
Contributor

fdncred commented Sep 5, 2024

I'm ok with this PR as long as I don't get an error every time I run git log and similar commands that return non-zero exit codes when there isn't an error.

@IanManske
Copy link
Copy Markdown
Member Author

Ok, let's see how this goes.

@IanManske IanManske merged commit 3d008e2 into nushell:main Sep 7, 2024
@hustcer hustcer added this to the v0.98.0 milestone Sep 7, 2024
@IanManske IanManske mentioned this pull request Sep 8, 2024
IanManske added a commit that referenced this pull request Sep 10, 2024
# Description
Fixes a bug in the IR for `try` to match that of the regular evaluator
(continuing from #13515):
```nushell
# without IR:
try { ^false } catch { 'caught' } # == 'caught'

# with IR:
try { ^false } catch { 'caught' } # error, non-zero exit code
```

In this PR, both now evaluate to `caught`. For the implementation, I had
to add another instruction, and feel free to suggest better
alternatives. In the future, it might be possible to get rid of this
extra instruction.

# User-Facing Changes
Bug fix, `try { ^false } catch { 'caught' }` now works in IR.
@wthueb
Copy link
Copy Markdown

wthueb commented Sep 24, 2024

This PR makes it so that nonzero exit codes are treated as errors, so an error would be triggered in that example. In interactive mode / the REPL, no error message will be displayed by default though. If a non-zero exit code needs to be ignored/caught in, e.g., a script, then complete or try can be used (this is the same as in nushell today).

@IanManske Is this still the case? I'm currently using nushell 0.98.0 in a terminal, and sure enough, git log followed by hitting q to quit it causes it to error:

Error: nu::shell::non_zero_exit_code

  × External command had a non-zero exit code
   ╭─[entry #25:1:1]
 1 │ git log
   · ─┬─
   ·  ╰── exited with code 128
   ╰────

Is there a config setting that I'm missing?

Edit: disregard, just read the changelog. There are new config settings $env.config.display_errors.exit_code and $env.config.display_errors.termination_signal outlined here: https://www.nushell.sh/blog/2024-09-17-nushell_0_98_0.html#non-zero-exit-codes-are-now-errors-toc

@132ikl
Copy link
Copy Markdown
Member

132ikl commented Sep 24, 2024

@wthueb Glad you found the setting. I just made a PR to highlight this in the release notes: nushell/nushell.github.io#1562

@DoumanAsh
Copy link
Copy Markdown

DoumanAsh commented Oct 10, 2024

This option MUST be false without config value
It is very annoying to update nushell and stuff breaking apart from every minor PR like this
I do not have time to read every PR to see how you break stuff

@IanManske
Copy link
Copy Markdown
Member Author

IanManske commented Oct 10, 2024

This option MUST be false without config value It is very annoying to update nushell and stuff breaking apart from every minor PR like this I do not have time to read every PR to see how you break stuff

That's what the release notes are for. Nushell is still pre 1.0, so it is expected for there to be occansional breaking changes.

Anyways, I wouldn't be against making false the default value for REPL sessions (without config).

@fdncred
Copy link
Copy Markdown
Contributor

fdncred commented Oct 10, 2024

#13873 needs to be cleaned up for false to happen in the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deprecated:pr-errors Deprecated: use A:error-handling, A:error-unhelpful, or A:error-silent-fail instead deprecated:pr-language (deprecated: this label is too vague) This PR makes some language changes notes:breaking-changes This PR implies a change affecting users and has to be noted in the release notes notes:mention Include the release notes summary in the "Hall of Fame" section

Projects

None yet

9 participants