NOTE: Claude Opus 5 did this investigation and wrote up the following report, while investigating the impact of this issue on a separate project.
This looks like a residual case of #9398. The fix in #9407 resolved the originally reported cases but appears to have moved the failure onto a neighboring path rather than eliminating it.
Re-raising a previously-rescued exception raises ArgumentError: circular causes on JRuby 10.1.1.0, when that exception was originally raised using the two-argument raise Class, arg form. CRuby returns the original exception. JRuby 10.0.4.0 and 10.1.0.0 also return the original exception, so this is new in 10.1.1.0.
This is the same nested-rescue shape as #9398, but with the inverse trigger: #9398 was about exceptions raised implicitly by the VM, which 10.1.1.0 now handles correctly. The explicitly-raised two-argument case regressed in the process.
Environment Information
jruby 10.1.1.0 (4.0.0) 2026-07-22 aaa95d57e1 Java HotSpot(TM) 64-Bit Server VM 24.0.1+9-30 on 24.0.1+9-30 +indy +jit [arm64-darwin]
Compared against:
ruby 4.0.6 (2026-07-14 revision 03b6d3f889) +PRISM [arm64-darwin23]
jruby 10.0.4.0 (3.4.5) 2026-03-03 9af05b916f Java HotSpot(TM) 64-Bit Server VM 24.0.1+9-30 on 24.0.1+9-30 +indy +jit [arm64-darwin]
jruby 10.1.0.0 (4.0.0) 2026-04-20 32f988b78c Java HotSpot(TM) 64-Bit Server VM 24.0.1+9-30 on 24.0.1+9-30 +indy +jit [arm64-darwin]
Expected Behavior
The minimal case below should propagate RuntimeError: inner, as it does on CRuby 4.0, JRuby 10.0.4.0, and JRuby 10.1.0.0.
begin
raise RuntimeError, "inner" # explicit two-argument raise
rescue RuntimeError => inner
begin
raise ArgumentError, "wrapper" # nested raise; its cause is `inner`
rescue ArgumentError
raise inner # re-raise the outer-rescued exception
end
end
Actual Behavior
On JRuby 10.1.1.0 the raise inner line raises ArgumentError: circular causes.
$ ruby minimal.rb # CRuby 4.0.6
minimal.rb:2:in '<main>': inner (RuntimeError)
$ jruby minimal.rb # 10.0.4.0 and 10.1.0.0
RuntimeError: inner
<main> at minimal.rb:2
$ jruby minimal.rb # 10.1.1.0
ArgumentError: circular causes
<main> at minimal.rb:7
Workaround
Passing an explicit cause: nil on the re-raise avoids it, and behaves identically on CRuby and all tested versions of JRuby:
This suppresses only the implicit adoption of $! as the cause. It does not clear a cause the exception already carries, so an existing cause chain is preserved on both runtimes.
Notes
Some observations that may help narrow this down.
It depends on the raise form, not the exception class. Only the two-argument raise Class, arg form triggers it. raise Class.new(arg), raise Class, and raise "string" are all unaffected, as are exceptions raised implicitly by the VM.
Within that form, every class I tried is affected, including NameError and ArgumentError — the two classes named in the #9407 discussion as having gained explicit cause handling. So the fix appears to have addressed the implicit/VM-raised path specifically, leaving the explicit two-argument path still not recording a cause.
That matches the mechanism described in #9398: an exception that does not get a nil cause recorded early enough will later try to implicitly adopt $! as its cause, and errors out when that turns out to be circular instead of declining silently as CRuby does.
Also note: I bisected this to the 10.1.0.0 → 10.1.1.0 release window, not to an individual commit. The attribution to #9407 is inferred from that window and from the discussion in #9398, so it is worth confirming.
NOTE: Claude Opus 5 did this investigation and wrote up the following report, while investigating the impact of this issue on a separate project.
This looks like a residual case of #9398. The fix in #9407 resolved the originally reported cases but appears to have moved the failure onto a neighboring path rather than eliminating it.
Re-raising a previously-rescued exception raises
ArgumentError: circular causeson JRuby 10.1.1.0, when that exception was originally raised using the two-argumentraise Class, argform. CRuby returns the original exception. JRuby 10.0.4.0 and 10.1.0.0 also return the original exception, so this is new in 10.1.1.0.This is the same nested-rescue shape as #9398, but with the inverse trigger: #9398 was about exceptions raised implicitly by the VM, which 10.1.1.0 now handles correctly. The explicitly-raised two-argument case regressed in the process.
Environment Information
Compared against:
Expected Behavior
The minimal case below should propagate
RuntimeError: inner, as it does on CRuby 4.0, JRuby 10.0.4.0, and JRuby 10.1.0.0.Actual Behavior
On JRuby 10.1.1.0 the
raise innerline raisesArgumentError: circular causes.Workaround
Passing an explicit
cause: nilon the re-raise avoids it, and behaves identically on CRuby and all tested versions of JRuby:This suppresses only the implicit adoption of
$!as the cause. It does not clear a cause the exception already carries, so an existing cause chain is preserved on both runtimes.Notes
Some observations that may help narrow this down.
It depends on the raise form, not the exception class. Only the two-argument
raise Class, argform triggers it.raise Class.new(arg),raise Class, andraise "string"are all unaffected, as are exceptions raised implicitly by the VM.Within that form, every class I tried is affected, including
NameErrorandArgumentError— the two classes named in the #9407 discussion as having gained explicit cause handling. So the fix appears to have addressed the implicit/VM-raised path specifically, leaving the explicit two-argument path still not recording a cause.That matches the mechanism described in #9398: an exception that does not get a
nilcause recorded early enough will later try to implicitly adopt$!as its cause, and errors out when that turns out to be circular instead of declining silently as CRuby does.Also note: I bisected this to the 10.1.0.0 → 10.1.1.0 release window, not to an individual commit. The attribution to #9407 is inferred from that window and from the discussion in #9398, so it is worth confirming.