From 19dd37999aecb657448fda793a939c82d144b0de Mon Sep 17 00:00:00 2001 From: Hamza Tarar Date: Thu, 12 Nov 2026 18:20:47 +0500 Subject: [PATCH] Save history independently of the locale encoding Opening the history file with the locale's encoding makes f.puts raise Encoding::UndefinedConversionError for any entry holding a character the locale cannot represent. The rescues around it only cover Errno, so the exception escapes save_history, which runs from the ensure block in IRB.run, and IRB aborts on exit with the whole session discarded. Write the entries in the encoding they already carry instead. Fixes #1023 --- lib/irb/history.rb | 6 +++++- test/irb/test_history.rb | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/irb/history.rb b/lib/irb/history.rb index 1b0f2bc59..4d360e625 100644 --- a/lib/irb/history.rb +++ b/lib/irb/history.rb @@ -76,7 +76,11 @@ def save_history begin ensure_history_file_permissions(history_file) - File.open(history_file, (append_history ? "a" : "w"), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f| + # Write the entries in the encoding they already carry. Forcing the + # locale's encoding raises Encoding::UndefinedConversionError on a + # non-ASCII entry under an ASCII locale, and the rescues below do not + # catch it, so IRB aborts on exit and the whole session is lost. + File.open(history_file, (append_history ? "a" : "w"), 0o600) do |f| hist = history.map { |l| l.scrub.split("\n").join("\\\n") } unless append_history || History.infinite? diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index e600c74cb..979611414 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -163,6 +163,23 @@ def test_history_different_encodings INPUT end + def test_history_saves_non_ascii_input_under_an_ascii_locale + IRB.conf[:SAVE_HISTORY] = 2 + IRB.conf[:LC_MESSAGES] = IRB::Locale.new("en_US.ASCII") + ENV["HOME"] = @tmpdir + + io = TestInputMethodWithRelineHistory.new + io.class::HISTORY.clear + io.class::HISTORY << "puts 'h\u00e9llo'" + + assert_nothing_raised do + io.save_history + end + + assert_equal("puts 'h\u00e9llo'\n", + File.read(IRB.rc_file("_history"), encoding: Encoding::UTF_8)) + end + def test_history_does_not_raise_when_history_file_directory_does_not_exist backup_history_file = IRB.conf[:HISTORY_FILE] IRB.conf[:SAVE_HISTORY] = 1