Mike Slinn
Mike Slinn

Graceful Crash Exit From Ruby

Published 2023-08-11.
Time to read: 1 minutes.

This page is part of the ruby collection, categorized under Jekyll, Ruby.

Ever want to stop your Ruby program, without generating a stack trace? It is simple enough to do if you control the execution environment, but if your program is loaded by another program then the loading program can trap your attempts to exit.

Jekyll traps exceptions thrown by its plugins. If you attempt to call abort or exit, those calls will work, but there is nothing you can do to prevent a long stack trace from being issued. This is not a pleasant experience for users.


You might try calling Process.kill, like this:

Process.kill('KILL', Process.pid) # Like kill -9 in bash
# or:
Process.kill('SEGV', Process.pid) # Like kill -11 in bash

... unfortunately, not only does this generate a stack trace, it also dumps lots of other information. This is worse than just calling abort.


The only way I found that worked was to terminate the process by loading another process, using Kernel:exec.

The following method prints an error message in red, using Colorator, then replaces the Jekyll process with the bash command, echo ''.

Ruby code
require 'colorator'

# If a Jekyll plugin needs to crash exit, and stop Jekyll, call this method.
# It does not generate a stack trace.
# This method does not return because the process is abruptly terminated.
# @param error StandardError or a subclass of StandardError is required
# Do not raise the error before calling this method, just create it via 'new', like this:
# exit_without_stack_trace StandardError.new('This is my error message')
# If you want to call this method from a handler method, the default index for the backtrace array must be specified.
# The default backtrace index is 1, which means the calling method.
# To specify the calling method's caller, pass in 2, like this:
# exit_without_stack_trace StandardError.new('This is my error message'), 2
def exit_without_stack_trace(error, caller_index = 1)
  raise error
rescue StandardError => e
  file, line_number, caller = e.backtrace[caller_index].split(':')
  caller = caller.tr('`', "'")
  warn "#{self.class} died with a '#{error.message}' #{caller} on line #{line_number} of #{file}".red
  exec "echo ''"