# debug.rb: the Ruby debugger that replaced the standard library one

> debug.rb is a Ruby library providing debugging functionality to MRI 2.7 and later, written as a replacement for the traditional lib/debug.rb standard library that was implemented with set_trace_func. The rewrite is faster when you are not stepping, supports remote debugging over a UNIX domain socket or TCP, and can drive a console, VSCode or Chrome DevTools from the same session.

**ruby/debug** — Debugging functionality for Ruby

- Repository: https://github.com/ruby/debug
- Stars: 1,276 · Forks: 148
- Language: Ruby
- License: BSD-2-Clause
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/ruby-debug

## A rewrite of the standard library debugger

The README states the position directly. This library provides debugging functionality to Ruby, MRI, 2.7 and later, and this debug.rb is the replacement of the traditional lib/debug.rb standard library, which was implemented by set_trace_func.

That last clause is the whole design story. The old standard library debugger was built on set_trace_func, which is a global trace hook: every method call, return, raise and line event in the process routed through a Ruby-level callback. That is an expensive mechanism to leave switched on, which is why the old debugger was slow, and it is also why it could only ever be a foreground, same-machine experience.

The rewrite lists the advantages it delivers. Fast, with no performance penalty on non-stepping mode and non-breakpoints, which is the property that made a debugger unusable as a permanent dependency before. Remote debugging supported natively, over a UNIX domain socket and over TCP/IP, and with integration for rich debugger frontends. And extensible, with the application able to introduce debugging support in several ways.

The extension points are worth noting because they are unusual. You can bring in the debugger by the rdbg command, by loading libraries with the -r command line option, or by calling Ruby's method explicitly. The third of those is what makes the library usable from inside a test suite or a script without modifying source.

The miscellaneous list gives a sense of where the work is. Support for threads is marked as almost done, and support for Ractors is marked as TODO. There is support for suspending and entering the debug console with Ctrl-C at most of timing. The backtrace command shows parameters. And there is support for recording and replay debugging.

The licence is BSD-2-Clause, and the last push was on 2026-06-12.

## Three frontends over the same session

The remote debugging table in the README is the clearest summary of what the rewrite bought.

| Frontend | Console | VSCode | Chrome DevTool |
| Connection | UDS, TCP/IP | UDS, TCP/IP | TCP/IP |
| Requirement | None | vscode-rdbg | Chrome |

The console frontend is the traditional one, and it needs nothing beyond the gem itself. The VSCode integration needs the vscode-rdbg extension from the marketplace, and the Chrome DevTools integration needs a browser.

What makes this worth building is that all three are the same session. The remote debugging is not a separate feature with a separate protocol, it is the same debugger with a transport attached, so the breakpoints and the state are the same object whichever frontend you connect to.

The transport choice matters for security reasons that the table implies rather than states. A UNIX domain socket is filesystem-scoped, so it inherits the permissions of the socket file and cannot be reached from another machine. TCP/IP has no such restriction, so a debug session listening on a TCP port is reachable by anything that can route to it.

The README does not describe authentication for the TCP transport in the section given here, so a team exposing a debug session over TCP needs to establish that the network path is trusted before they do it. That is the kind of question the connection row of the table should prompt, whether or not the answer is in the document.

## binding.break for the edit-and-run workflow

The first way to use the debugger is the one most Ruby developers will try first, because it is the same shape as binding.pry and binding.irb.

If you can modify the source code, you can use the debugger by adding require 'debug' at the top of your program and putting the binding.break method into lines where you want to stop as breakpoints. binding.break has two aliases which do the same thing, binding.b and debugger.

```ruby
require 'debug'

a = 1
b = 2
binding.break                          # Program will stop here
c = 3
d = 4
binding.break                          # Program will stop here
p [a, b, c, d]
```

The console output in the README shows the two stops with the source listing and the current line marked, and the (rdbg) prompt afterwards. The info locals command then shows the local variables, and the output differs between the two stops, because the second one is reached after c and d have been assigned. That is the whole point of the workflow: the same breakpoint code, two different states, and the ability to see the difference.

The last note in the README about the alternative to source modification covers the case where you forgot the breakpoint. If you started a program and forgot to set the alert, suspend the program with Ctrl+Z and then enter fg; telegram-send, which is the same idea for the debugger: fg; ruby-debugger or the rdbg command to pick up execution at the point you suspended.

The tradeoff of this approach is the one every Ruby developer already knows: you have to add and then remove the require and the calls. The next approach avoids that.

## rdbg for a session you start from outside the program

The second invocation path exists for when you do not want to modify the source code. You use the rdbg command, or bundle exec rdbg, to run the program with the debugger, which is similar to how you would launch a program with ruby program.rb.

```console
$ rdbg target.rb
DEBUGGER: Session start (pid: 7656)
[1, 7] in target.rb
=>    1| a = 1
      2| b = 2
      3| c = 3
      4| d = 4
      5| p [a, b, c, d]
      6|
      7| __END__
=>#0    <main> at target.rb:1

(rdbg)
```

The rdbg command suspends the program at the beginning of the given script and you can use debug commands to control execution from there. The (rdbg) is the console prompt.

From there, breakpoints are set with the break command, or b for short, and the README shows both a numbered form and the short form.

```console
(rdbg) break 3                         # set breakpoint at line 3
#0  BP - Line  /mnt/c/ko1/src/rb/ruby-debug/target.rb:3 (line)

(rdbg) b 5                             # set breakpoint at line 5
#1  BP - Line  /mnt/c/ko1/src/rb/ruby-debug/target.rb:5 (line)

(rdbg) break                           # show all registered breakpoints
```

Note that the program starts suspended at line 1, not at the first breakpoint. That is a deliberate difference from a compiled-language debugger, and it is a useful one: you are inside the process before the first line of your own code has run, so you can set breakpoints, inspect constants, or change state before anything happens.

The repository layout supports the two-part structure this implies. There is an ext/ directory, which holds the native extension, a lib/ directory for the Ruby side, an exe/ directory for the rdbg executable, a bin/ directory, a debug.gemspec, a Rakefile for the build, and a test/ directory. A debugger that has to be fast enough not to penalise a running program is one with a native component, and the ext/ directory is that component.

## Installation, and the version constraint that is not optional

Installing is one command.

```console
$ gem install debug
```

Alternatively, you can specify -Ipath/to/debug/lib in RUBYOPT, or as a Ruby command-line option, and the README says this is especially useful for debugging this gem during development.

For Bundler users the Gemfile entry is this.

```ruby
gem "debug", ">= 1.0.0"
```

And the parenthetical warning is the important part of that snippet. The version constraint is important, and the reason is spelled out: debug < 1.0.0 is an older, abandoned gem that is completely different from this product.

That is a real trap rather than a formality. There was previously a separate gem also called debug, and it was abandoned. A Gemfile that says gem "debug" without a constraint can resolve to that old package, and the failure will look like the debugger is missing features rather than like a wrong gem. The >= 1.0.0 constraint is what distinguishes the modern debugger from the abandoned one.

The repository also carries a TODO.md at the root, which is where the Ractor support item and other outstanding work are tracked, and the two CI workflows visible in the badges are a ruby.yml workflow and a protocol.yml workflow, the latter presumably covering the remote protocol.

The CONTRIBUTING.md is present, and the licence file is LICENSE.txt, matching the BSD-2-Clause identifier.

## A 1.x line, released twice in 2025

The release history shows v1.11.1 from 2025-12-19, v1.11.0 from 2025-06-17 and v1.10.0 from 2024-12-18, and the last push was on 2026-06-12.

The pattern is a steady minor-version cadence, roughly every six months, with patch releases following the minor ones. That is the rhythm of a tool that has to track the Ruby versions it supports rather than a tool shipping features weekly.

Given the MRI 2.7 and later requirement, the library has to work across a range of Ruby implementations and their internal APIs, and the trace and breakpoint machinery is closely tied to the interpreter internals. That constraint is what shapes the release cadence, and it is also why the Ractor support is still marked as TODO rather than merely unfinished.

Ractors are the direction Ruby itself is moving, and a debugger that cannot attach to a Ractor is a debugger that will not work on code written in the next few years of Ruby. The README is straightforward about it, listing support for Ractors as TODO alongside the note that thread support is almost done. The distinction between almost done and TODO is not cosmetic; it says that the existing multi-thread story is essentially working and the Ractor story has not started.

The practical advice for an adopter is to treat the minor version as the compatibility unit. Because the library is bound to interpreter internals, a Ruby upgrade is the event most likely to require a debug.rb upgrade, and pinning to the latest 1.x is the low-risk choice unless you have a reason not to.

## Conclusion

debug.rb suits a Ruby developer on MRI 2.7 or later who wants breakpoints, a console, remote sessions and a graphical frontend from one tool, and it is the debugger to reach for now that it has replaced the standard library version. It is the wrong tool if your Ruby is not MRI, since the README scopes it to MRI, and it is the wrong choice if you need Ractor support, which the feature list still marks as TODO. Before you adopt it, pin the gem to 1.0.0 or newer, because the README warns that debug below 1.0.0 is an older, abandoned gem with the same name and a completely different product.

## FAQ

### How do I install the Ruby debug gem?

Run gem install debug. Alternatively specify -Ipath/to/debug/lib in RUBYOPT or as a Ruby command-line option, which the README says is especially useful for debugging the gem during development. With Bundler, add gem "debug", ">= 1.0.0" to your Gemfile.

### Why does the Gemfile entry need a version constraint?

The README states the constraint is important because debug below 1.0.0 is an older, abandoned gem that is completely different from this product, so an unconstrained gem "debug" can resolve to the wrong package.

### What is the difference between binding.break and the rdbg command?

binding.break requires you to add require 'debug' and put binding.break calls in the source, similar to binding.pry, while rdbg runs the program with the debugger from the command line so you do not modify the source at all.

### Which frontends can connect to a debug.rb session?

The console, VSCode and Chrome DevTools, over a UNIX domain socket or TCP/IP, with VSCode requiring the vscode-rdbg extension and Chrome DevTools requiring a browser.

### Does debug.rb support Ruby's Ractors?

Not yet. The README's feature list marks support for threads as almost done and support for Ractors as TODO, so a session cannot currently attach to code running in a Ractor.

## Sources

- [Issues](https://github.com/ruby/debug/issues)
- [License: BSD-2-Clause](https://github.com/ruby/debug/blob/master/LICENSE)
- [README](https://github.com/ruby/debug/blob/master/README.md)
- [Releases](https://github.com/ruby/debug/releases)
- [ruby/debug on GitHub](https://github.com/ruby/debug)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ruby-debug
