Skip to content

feat: improve NonZeroExitError message#151

Open
bluwy wants to merge 4 commits into
tinylibs:mainfrom
bluwy:improve-error
Open

feat: improve NonZeroExitError message#151
bluwy wants to merge 4 commits into
tinylibs:mainfrom
bluwy:improve-error

Conversation

@bluwy

@bluwy bluwy commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

The default printed error message is a bit wordy and doesn't provide a lot of information. I fixed this by displaying the command that caused the error, and hide the result from the logs by making it non-enumerable.

Ideally, we don't pass the entire process object willy nilly, but it could potentially break users who would access it even if untyped. I went with the least breaking approach for now but making it non-enumerable.

Also, I made exitCode non-nullable. Since it's called NonZeroExitError, there's already an exit code.


With:

import {exec} from './dist/main.mjs';

await exec('git', ['rev-parse', 'abc'], {throwOnError: true});

Before:

file:///Users/bjorn/Work/oss/tinyexec/dist/main.mjs:284
                if (this._options.throwOnError && this.exitCode !== 0 && this.exitCode !== void 0) throw new k(this, i);
                                                                                                         ^

k [Error]: Process exited with non-zero status (128)
    at I._waitForOutput (file:///Users/bjorn/Work/oss/tinyexec/dist/main.mjs:284:92)
    at async file:///Users/bjorn/Work/oss/tinyexec/test.mjs:3:1 {
  result: I {
    _process: ChildProcess {
      _events: [Object: null prototype] {},
      _eventsCount: 0,
      _maxListeners: undefined,
      _closesNeeded: 3,
      _closesGot: 3,
      connected: false,
      signalCode: null,
      exitCode: 128,
      killed: false,
      spawnfile: 'git',
      _handle: null,
      spawnargs: [ 'git', 'rev-parse', 'abc' ],
      pid: 63048,
      stdin: Socket {
        connecting: false,
        _hadError: false,
        _parent: null,
        _host: null,
        _closeAfterHandlingError: false,
        _events: {
          close: undefined,
          error: undefined,
          prefinish: undefined,
          finish: undefined,
          drain: undefined,
          data: undefined,
          end: [Function: onReadableStreamEnd],
          readable: undefined
        },
        _readableState: ReadableState {
          highWaterMark: 65536,
          buffer: [],
          bufferIndex: 0,
          length: 0,
          pipes: [],
          awaitDrainWriters: null,
          readable: false,
          Symbol(kState): 1054580
        },
        _writableState: WritableState {
          highWaterMark: 65536,
          length: 0,
          corked: 0,
          onwrite: [Function (anonymous)],
          writelen: 0,
          bufferedIndex: 0,
          pendingcb: 0,
          Symbol(kState): 17564532,
          Symbol(kBufferedValue): null
        },
        allowHalfOpen: false,
        _maxListeners: undefined,
        _eventsCount: 1,
        _sockname: null,
        _pendingData: null,
        _pendingEncoding: '',
        server: null,
        _server: null,
        Symbol(async_id_symbol): 7,
        Symbol(kHandle): null,
        Symbol(lastWriteQueueSize): 0,
        Symbol(timeout): null,
        Symbol(kBuffer): null,
        Symbol(kBufferCb): null,
        Symbol(kBufferGen): null,
        Symbol(shapeMode): true,
        Symbol(kCapture): false,
        Symbol(kSetNoDelay): false,
        Symbol(kSetKeepAlive): false,
        Symbol(kSetKeepAliveInitialDelay): 0,
        Symbol(kSetTOS): undefined,
        Symbol(kBytesRead): 0,
        Symbol(kBytesWritten): 0
      },
      stdout: Socket {
        connecting: false,
        _hadError: false,
        _parent: null,
        _host: null,
        _closeAfterHandlingError: false,
        _events: {
          close: [
            [Function (anonymous)],
            [Function: onclose],
            Symbol(events.emitting): 0
          ],
          error: [Function: onerror],
          prefinish: undefined,
          finish: [Function: onfinish],
          drain: undefined,
          data: undefined,
          end: [
            [Function: onReadableStreamEnd],
            [Function: onend],
            Symbol(events.emitting): 0
          ],
          readable: [Function: next]
        },
        _readableState: ReadableState {
          highWaterMark: 65536,
          buffer: [],
          bufferIndex: 0,
          length: 0,
          pipes: [],
          awaitDrainWriters: null,
          Symbol(kState): 9996148
        },
        _writableState: WritableState {
          highWaterMark: 65536,
          length: 0,
          corked: 0,
          onwrite: [Function (anonymous)],
          writelen: 0,
          bufferedIndex: 0,
          pendingcb: 0,
          Symbol(kState): 1091310964,
          Symbol(kBufferedValue): null
        },
        allowHalfOpen: false,
        _maxListeners: undefined,
        _eventsCount: 5,
        _sockname: null,
        _pendingData: null,
        _pendingEncoding: '',
        server: null,
        _server: null,
        write: [Function: writeAfterFIN],
        Symbol(async_id_symbol): 8,
        Symbol(kHandle): null,
        Symbol(lastWriteQueueSize): 0,
        Symbol(timeout): null,
        Symbol(kBuffer): null,
        Symbol(kBufferCb): null,
        Symbol(kBufferGen): null,
        Symbol(shapeMode): true,
        Symbol(kCapture): false,
        Symbol(kSetNoDelay): false,
        Symbol(kSetKeepAlive): false,
        Symbol(kSetKeepAliveInitialDelay): 0,
        Symbol(kSetTOS): undefined,
        Symbol(kBytesRead): 4,
        Symbol(kBytesWritten): 0
      },
      stderr: Socket {
        connecting: false,
        _hadError: false,
        _parent: null,
        _host: null,
        _closeAfterHandlingError: false,
        _events: {
          close: [
            [Function (anonymous)],
            [Function: onclose],
            Symbol(events.emitting): 0
          ],
          error: [Function: onerror],
          prefinish: undefined,
          finish: [Function: onfinish],
          drain: undefined,
          data: undefined,
          end: [
            [Function: onReadableStreamEnd],
            [Function: onend],
            Symbol(events.emitting): 0
          ],
          readable: [Function: next]
        },
        _readableState: ReadableState {
          highWaterMark: 65536,
          buffer: [],
          bufferIndex: 0,
          length: 0,
          pipes: [],
          awaitDrainWriters: null,
          Symbol(kState): 9996148
        },
        _writableState: WritableState {
          highWaterMark: 65536,
          length: 0,
          corked: 0,
          onwrite: [Function (anonymous)],
          writelen: 0,
          bufferedIndex: 0,
          pendingcb: 0,
          Symbol(kState): 1091310964,
          Symbol(kBufferedValue): null
        },
        allowHalfOpen: false,
        _maxListeners: undefined,
        _eventsCount: 5,
        _sockname: null,
        _pendingData: null,
        _pendingEncoding: '',
        server: null,
        _server: null,
        write: [Function: writeAfterFIN],
        Symbol(async_id_symbol): 9,
        Symbol(kHandle): null,
        Symbol(lastWriteQueueSize): 0,
        Symbol(timeout): null,
        Symbol(kBuffer): null,
        Symbol(kBufferCb): null,
        Symbol(kBufferGen): null,
        Symbol(shapeMode): true,
        Symbol(kCapture): false,
        Symbol(kSetNoDelay): false,
        Symbol(kSetKeepAlive): false,
        Symbol(kSetKeepAliveInitialDelay): 0,
        Symbol(kSetTOS): undefined,
        Symbol(kBytesRead): 184,
        Symbol(kBytesWritten): 0
      },
      stdio: [
        Socket {
          connecting: false,
          _hadError: false,
          _parent: null,
          _host: null,
          _closeAfterHandlingError: false,
          _events: {
            close: undefined,
            error: undefined,
            prefinish: undefined,
            finish: undefined,
            drain: undefined,
            data: undefined,
            end: [Function: onReadableStreamEnd],
            readable: undefined
          },
          _readableState: ReadableState {
            highWaterMark: 65536,
            buffer: [],
            bufferIndex: 0,
            length: 0,
            pipes: [],
            awaitDrainWriters: null,
            readable: false,
            Symbol(kState): 1054580
          },
          _writableState: WritableState {
            highWaterMark: 65536,
            length: 0,
            corked: 0,
            onwrite: [Function (anonymous)],
            writelen: 0,
            bufferedIndex: 0,
            pendingcb: 0,
            Symbol(kState): 17564532,
            Symbol(kBufferedValue): null
          },
          allowHalfOpen: false,
          _maxListeners: undefined,
          _eventsCount: 1,
          _sockname: null,
          _pendingData: null,
          _pendingEncoding: '',
          server: null,
          _server: null,
          Symbol(async_id_symbol): 7,
          Symbol(kHandle): null,
          Symbol(lastWriteQueueSize): 0,
          Symbol(timeout): null,
          Symbol(kBuffer): null,
          Symbol(kBufferCb): null,
          Symbol(kBufferGen): null,
          Symbol(shapeMode): true,
          Symbol(kCapture): false,
          Symbol(kSetNoDelay): false,
          Symbol(kSetKeepAlive): false,
          Symbol(kSetKeepAliveInitialDelay): 0,
          Symbol(kSetTOS): undefined,
          Symbol(kBytesRead): 0,
          Symbol(kBytesWritten): 0
        },
        Socket {
          connecting: false,
          _hadError: false,
          _parent: null,
          _host: null,
          _closeAfterHandlingError: false,
          _events: {
            close: [Array],
            error: [Function: onerror],
            prefinish: undefined,
            finish: [Function: onfinish],
            drain: undefined,
            data: undefined,
            end: [Array],
            readable: [Function: next]
          },
          _readableState: ReadableState {
            highWaterMark: 65536,
            buffer: [],
            bufferIndex: 0,
            length: 0,
            pipes: [],
            awaitDrainWriters: null,
            Symbol(kState): 9996148
          },
          _writableState: WritableState {
            highWaterMark: 65536,
            length: 0,
            corked: 0,
            onwrite: [Function (anonymous)],
            writelen: 0,
            bufferedIndex: 0,
            pendingcb: 0,
            Symbol(kState): 1091310964,
            Symbol(kBufferedValue): null
          },
          allowHalfOpen: false,
          _maxListeners: undefined,
          _eventsCount: 5,
          _sockname: null,
          _pendingData: null,
          _pendingEncoding: '',
          server: null,
          _server: null,
          write: [Function: writeAfterFIN],
          Symbol(async_id_symbol): 8,
          Symbol(kHandle): null,
          Symbol(lastWriteQueueSize): 0,
          Symbol(timeout): null,
          Symbol(kBuffer): null,
          Symbol(kBufferCb): null,
          Symbol(kBufferGen): null,
          Symbol(shapeMode): true,
          Symbol(kCapture): false,
          Symbol(kSetNoDelay): false,
          Symbol(kSetKeepAlive): false,
          Symbol(kSetKeepAliveInitialDelay): 0,
          Symbol(kSetTOS): undefined,
          Symbol(kBytesRead): 4,
          Symbol(kBytesWritten): 0
        },
        Socket {
          connecting: false,
          _hadError: false,
          _parent: null,
          _host: null,
          _closeAfterHandlingError: false,
          _events: {
            close: [Array],
            error: [Function: onerror],
            prefinish: undefined,
            finish: [Function: onfinish],
            drain: undefined,
            data: undefined,
            end: [Array],
            readable: [Function: next]
          },
          _readableState: ReadableState {
            highWaterMark: 65536,
            buffer: [],
            bufferIndex: 0,
            length: 0,
            pipes: [],
            awaitDrainWriters: null,
            Symbol(kState): 9996148
          },
          _writableState: WritableState {
            highWaterMark: 65536,
            length: 0,
            corked: 0,
            onwrite: [Function (anonymous)],
            writelen: 0,
            bufferedIndex: 0,
            pendingcb: 0,
            Symbol(kState): 1091310964,
            Symbol(kBufferedValue): null
          },
          allowHalfOpen: false,
          _maxListeners: undefined,
          _eventsCount: 5,
          _sockname: null,
          _pendingData: null,
          _pendingEncoding: '',
          server: null,
          _server: null,
          write: [Function: writeAfterFIN],
          Symbol(async_id_symbol): 9,
          Symbol(kHandle): null,
          Symbol(lastWriteQueueSize): 0,
          Symbol(timeout): null,
          Symbol(kBuffer): null,
          Symbol(kBufferCb): null,
          Symbol(kBufferGen): null,
          Symbol(shapeMode): true,
          Symbol(kCapture): false,
          Symbol(kSetNoDelay): false,
          Symbol(kSetKeepAlive): false,
          Symbol(kSetKeepAliveInitialDelay): 0,
          Symbol(kSetTOS): undefined,
          Symbol(kBytesRead): 184,
          Symbol(kBytesWritten): 0
        }
      ],
      Symbol(shapeMode): false,
      Symbol(kCapture): false
    },
    _aborted: false,
    _options: { timeout: undefined, persist: false, throwOnError: true },
    _command: 'git',
    _args: [ 'rev-parse', 'abc' ],
    _resolveClose: [Function (anonymous)],
    _processClosed: Promise { undefined },
    _thrownError: undefined,
    _streamOut: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: null,
      _closeAfterHandlingError: false,
      _events: {
        close: [
          [Function (anonymous)],
          [Function: onclose],
          Symbol(events.emitting): 0
        ],
        error: [Function: onerror],
        prefinish: undefined,
        finish: [Function: onfinish],
        drain: undefined,
        data: undefined,
        end: [
          [Function: onReadableStreamEnd],
          [Function: onend],
          Symbol(events.emitting): 0
        ],
        readable: [Function: next]
      },
      _readableState: ReadableState {
        highWaterMark: 65536,
        buffer: [],
        bufferIndex: 0,
        length: 0,
        pipes: [],
        awaitDrainWriters: null,
        Symbol(kState): 9996148
      },
      _writableState: WritableState {
        highWaterMark: 65536,
        length: 0,
        corked: 0,
        onwrite: [Function (anonymous)],
        writelen: 0,
        bufferedIndex: 0,
        pendingcb: 0,
        Symbol(kState): 1091310964,
        Symbol(kBufferedValue): null
      },
      allowHalfOpen: false,
      _maxListeners: undefined,
      _eventsCount: 5,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: null,
      _server: null,
      write: [Function: writeAfterFIN],
      Symbol(async_id_symbol): 8,
      Symbol(kHandle): null,
      Symbol(lastWriteQueueSize): 0,
      Symbol(timeout): null,
      Symbol(kBuffer): null,
      Symbol(kBufferCb): null,
      Symbol(kBufferGen): null,
      Symbol(shapeMode): true,
      Symbol(kCapture): false,
      Symbol(kSetNoDelay): false,
      Symbol(kSetKeepAlive): false,
      Symbol(kSetKeepAliveInitialDelay): 0,
      Symbol(kSetTOS): undefined,
      Symbol(kBytesRead): 4,
      Symbol(kBytesWritten): 0
    },
    _streamErr: Socket {
      connecting: false,
      _hadError: false,
      _parent: null,
      _host: null,
      _closeAfterHandlingError: false,
      _events: {
        close: [
          [Function (anonymous)],
          [Function: onclose],
          Symbol(events.emitting): 0
        ],
        error: [Function: onerror],
        prefinish: undefined,
        finish: [Function: onfinish],
        drain: undefined,
        data: undefined,
        end: [
          [Function: onReadableStreamEnd],
          [Function: onend],
          Symbol(events.emitting): 0
        ],
        readable: [Function: next]
      },
      _readableState: ReadableState {
        highWaterMark: 65536,
        buffer: [],
        bufferIndex: 0,
        length: 0,
        pipes: [],
        awaitDrainWriters: null,
        Symbol(kState): 9996148
      },
      _writableState: WritableState {
        highWaterMark: 65536,
        length: 0,
        corked: 0,
        onwrite: [Function (anonymous)],
        writelen: 0,
        bufferedIndex: 0,
        pendingcb: 0,
        Symbol(kState): 1091310964,
        Symbol(kBufferedValue): null
      },
      allowHalfOpen: false,
      _maxListeners: undefined,
      _eventsCount: 5,
      _sockname: null,
      _pendingData: null,
      _pendingEncoding: '',
      server: null,
      _server: null,
      write: [Function: writeAfterFIN],
      Symbol(async_id_symbol): 9,
      Symbol(kHandle): null,
      Symbol(lastWriteQueueSize): 0,
      Symbol(timeout): null,
      Symbol(kBuffer): null,
      Symbol(kBufferCb): null,
      Symbol(kBufferGen): null,
      Symbol(shapeMode): true,
      Symbol(kCapture): false,
      Symbol(kSetNoDelay): false,
      Symbol(kSetKeepAlive): false,
      Symbol(kSetKeepAliveInitialDelay): 0,
      Symbol(kSetTOS): undefined,
      Symbol(kBytesRead): 184,
      Symbol(kBytesWritten): 0
    },
    _onError: [Function: _onError],
    _onClose: [Function: _onClose]
  },
  output: {
    stderr: "fatal: ambiguous argument 'abc': unknown revision or path not in the working tree.\n" +
      "Use '--' to separate paths from revisions, like this:\n" +
      "'git <command> [<revision>...] -- [<file>...]'\n",
    stdout: 'abc\n',
    exitCode: 128
  }
}

After:

file:///Users/bjorn/Work/oss/tinyexec/dist/main.mjs:291
                if (this._options.throwOnError && this.exitCode !== 0 && this.exitCode !== void 0) throw new k(this, i, this._command, this._args);
                                                                                                         ^

k [Error]: The command `git rev-parse abc` exited with a non-zero status (128)
    at I._waitForOutput (file:///Users/bjorn/Work/oss/tinyexec/dist/main.mjs:291:92)
    at async file:///Users/bjorn/Work/oss/tinyexec/test.mjs:3:1 {
  output: {
    stderr: "fatal: ambiguous argument 'abc': unknown revision or path not in the working tree.\n" +
      "Use '--' to separate paths from revisions, like this:\n" +
      "'git <command> [<revision>...] -- [<file>...]'\n",
    stdout: 'abc\n',
    exitCode: 128
  },
  exitCode: 128
}

Comment thread src/non-zero-exit-error.ts Outdated
Comment thread src/main.ts
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