Sorry about the previous suggestion. I've just noticed the actual cause of error. The following works as you'd assume it to:
def doCalc
  begin
    print( "Enter a number: " )
    aNum = gets().chomp()
    result = 100 / aNum.to_i
  #rescue Exception => e    # - dont catch Exception unless you REALLY need it.
  rescue => e               # - this catches StandardError and is usually enough
    result = 0
    puts( "Error: #{e}\nPlease try again." )   # <-- changed here
    retry   # retry on exception
  else
    msg = "Result = #{result}"
  ensure
    msg = "You entered '#{aNum}'. " + msg
  end
  return msg
end
You've actually had THREE errors thrown one after another.
First error ocurred as you planned - during the parsing and division. And it was caught by the rescue Exception => e properly.
But you cannot "add" an "exception" to a "string". Trying to "asd" + e + "asd" caused another error to be thrown what crashed your 'rescue' block. However, the immediatelly following 'ensure' block must run, so while your rescue block was about to quit and rise another error, the ensure block fired.
And the ensure block failed to add msg to the string, since msg was nil at the moment. So, for the third time an exception was raised, and it successfully has hidden all the previous exceptions.
In short: Note how I now replaced the add-exception-to-a-string with string interpolation. You cannot add e directly. You have to either use interpolation, or call e.to_s manually.