if you use fork/wait, you could get the exit code from the status code in the wait* functions:
int status;
if (wait(&status) != -1) {   // similar for waitpid, wait4, etc.
    if (WIFEXITED(status)) {
        exit_code = WEXITSTATUS(status);
    } else {
        // handle other conditions, e.g. signals.
    }
} else {
    // wait failed.
}

You could check the example of in the man page of wait(2).



Q:

Refactoring a String Calculator

I've refactored the string calculator kata as much as I could and tried to maintain single responsibility. Is there anything I could factor differently? The specs can be reviewed here.
module StringCalculator
  def self.add(string)
    string.end_with?("\n") ? fail('ends in newline') : solve(string)
  end

  def self.solve(string)
    verify(string)
    custom = delimiter(string)
    numerics = string.gsub(/\n/, custom).split(custom).map(&:to_i)
    numerics.reject { |n| n > 1000 }.reduce(0, :+)
  end

  def self.delimiter(string)
    string[0, 2] == '//' ? string[2, 1] : ','
  end

  def self.verify(string)
    find = string.scan(/-\d+/)
    fail("negatives not allowed: #{find.join(', ')}") if find.any?
  end

  private_class_method :verify, :delimiter, :solve
end

More importantly, I've thought of a case that involves the delimiter of a string that begins with // to be a number (which is considered invalid). I've made a quick way of solving that issue like so. Any suggestions on that?
Invalid case:
def self.delimiter(string)
  string[0, 2] == '//' ? check(string) || string[2, 1] : ','
end

def self.check(string)
  fail("invalid delimiter: #{string[2, 1]}") if string[2, 1] =~ /[0-9]/
end

A:

I think your code is pretty good.  The main opportunity for improved clarity I saw was to consolidate your validation code into one place.  In your code, validations are sprinkled across various methods, so that you are mixing the responsibility of "validation" and "work".  
Here's a refactoring (not tested) which puts all the validation in one place, and gets it out of the way up front.  T