Ruby

Debugging Ruby with pry-byebug (vs debug): 2026 Guide

Debugging Ruby with pry-byebug (vs debug): 2026 Guide

pry-byebug combines the pry REPL with byebug’s step debugger: add gem 'pry-byebug' to your Gemfile’s development/test group, drop binding.pry where you want to stop, then use step, next, finish, and continue. On Ruby 3.1+, the built-in debug gem (binding.break) is the default choice; pry-byebug remains best when you want pry’s REPL.

This guide is a reference for pry-byebug on current Rubies. Every command in this guide ran on Ruby 3.4.10 with pry-byebug 3.12.0.

pry-byebug at a Glance

Here are the facts most people search for, verified on a fresh install:

FactDetail
Current version3.12.0 (released January 2026)
byebug dependency~> 13.0 (a fresh install resolves to byebug 13.0.0)
pry dependency>= 0.13, < 0.17 (a fresh install resolves to pry 0.16.0)
Supported RubiesRuby 3.2 and newer (gemspec requirement)
Ruby 3.4 compatibilityYes: installed and stepped through code on Ruby 3.4.10
Maintenance statusActive again: 3.11.0 (March 2025) and 3.12.0 (January 2026) followed a 2022–2025 release gap
Repositorygithub.com/deivid-rodriguez/pry-byebug

pry-byebug 3.12.0 depends on byebug ~> 13.0 and pry >= 0.13, < 0.17. It requires Ruby 3.2 or newer, and it works on Ruby 3.4.

pry-byebug vs debug: Which to Use in 2026

Ruby 3.1 (released December 2021) started bundling the debug gem, and it has been the standard library debugger since. Ruby 3.4.10 ships debug 1.11.0. The Ruby core team maintains it alongside new Ruby releases, and its rdbg CLI supports remote sessions, VS Code, and Chrome DevTools through DAP and CDP (per the debug README).

byebug took a different path. Its C extension went almost five years without a release, from 11.1.3 (April 2020) to 12.0.0 (March 2025). Releases have resumed, but rough edges remain on new Rubies; you’ll meet one in the backtrace section.

pry-byebugdebug (built in)
InstallationGemfile entry, bundle installBundled with Ruby 3.1+
Breakpoint helperbinding.prybinding.break (also binding.b and debugger)
ConsoleFull pry REPL: ls, cd, show-source, syntax highlightingrdbg console with IRB integration
Remote and editor useNone built inrdbg --open, VS Code extension, Chrome DevTools (DAP, CDP)
Stepping enginebyebug C extension, community maintainedMaintained by the Ruby core team
Ruby support3.2+ in the current releaseEvery supported Ruby; bundled since 3.1

Verdict: default to debug on Ruby 3.1 or newer, and pick pry-byebug when pry’s REPL is the environment you want at your breakpoints.

For the built-in debugger’s own walkthrough, read our guide to debugging in Ruby with debug. The rest of this post covers pry-byebug.

Set Up pry-byebug for Ruby

Add the gem to the development and test groups of your Gemfile, so you can debug tests as well:

Ruby
group :development, :test do
  gem 'pry-byebug'
end

Then run bundle install. That’s the whole setup.

Basic Debugging with Breakpoints

A debugger replaces puts calls with direct access to a running program’s state. You add breakpoints to tell the interpreter where to stop.

Take a small bookstore that manages the titles it offers:

Ruby
require 'pry'
require 'pry-byebug'
 
class Book
  attr_accessor :title, :author, :price
 
  def initialize(title, author, price)
    @title = title
    @author = author
    @price = price
  end
end
 
class BookStore
  def initialize
    @books = []
  end
 
  def add_book(book)
    @books << book
  end
 
  def remove_book(title)
    @books.delete_if { |book| book.title == title }
  end
 
  def find_by_title(title)
    @books.find { |book| book.title.include?(title) }
  end
end
 
# Sample usage:
store = BookStore.new
book1 = Book.new("Dune", "Frank Herbert", 20.0)
book2 = Book.new("The Hobbit", "J.R.R. Tolkien", 15.0)
book3 = Book.new("Hobbit's Journey", "Unknown", 10.0)
 
store.add_book(book1)
store.add_book(book2)
store.add_book(book3)
 
puts store.find_by_title("Hobbit").title

The last call probably prints "The Hobbit", but two titles contain "Hobbit". To find out what happens, jump into the find_by_title method with a binding.pry breakpoint:

Ruby
def find_by_title(title)
  binding.pry
  @books.find { |book| book.title.include?(title) }
end

Run the program with ruby app.rb, and execution stops at the breakpoint:

Shell
From: app.rb:29 BookStore#find_by_title:
 
    27: def find_by_title(title)
    28:   binding.pry
 => 29:   @books.find { |book| book.title.include?(title) }
    30: end
 
[1] pry(#<BookStore>)>

You know exactly where you are: no guessing which part of the program printed a line to STDOUT. From this prompt, you can inspect title, call @books, and evaluate any Ruby expression in the current context. Here, the bug is the use of find, which returns one book where the caller may expect every match. Right in context, you can try select and compare results before you change the code.

Type continue to resume execution until the next breakpoint or the end of the program.

Stepping Commands: A Reference

Once you’ve stopped at a breakpoint, a handful of commands move you through the program. All of these ran as shown on Ruby 3.4.10:

CommandEffect
stepRun the next line, stepping into method calls
nextRun the next line in the current frame, stepping over calls
finishRun until the current frame returns
continueResume until the next breakpoint or the end of the program
up / downMove up or down the call stack without executing code
break BookStore#add_bookAdd a breakpoint at a method’s first line
break 36Add a breakpoint at line 36 of the current file
break ... if <expression>Only stop when the condition is truthy
break / break --delete 1List all breakpoints / delete breakpoint 1
!!!Exit the debugger and the program

To see stepping in action, move the breakpoint to the sample usage section, before the books are created:

Ruby
store = BookStore.new
binding.pry
book1 = Book.new("Dune", "Frank Herbert", 20.0)
book2 = Book.new("The Hobbit", "J.R.R. Tolkien", 15.0)
book3 = Book.new("Hobbit's Journey", "Unknown", 10.0)

The program stops before book1 is assigned. Type next to execute that line and stop at the following one:

Shell
From: app.rb:35 :
 
    33: store = BookStore.new
    34: binding.pry
 => 35: book1 = Book.new("Dune", "Frank Herbert", 20.0)
    36: book2 = Book.new("The Hobbit", "J.R.R. Tolkien", 15.0)
    37: book3 = Book.new("Hobbit's Journey", "Unknown", 10.0)
 
[1] pry(main)> next

Now book1 exists, and you can inspect it:

Shell
[1] pry(main)> book1
=> #<Book:0x0000702aa886b878 @author="Frank Herbert", @price=20.0, @title="Dune">

That beats a round of inserting, tweaking, and deleting puts calls.

Stepping Into Methods

next steps over method calls. To descend into initialize (called through Book.new), use step instead:

Shell
[1] pry(main)> step
 
From: app.rb:8 Book#initialize:
 
     7: def initialize(title, author, price)
 =>  8:   @title = title
     9:   @author = author
    10:   @price = price
    11: end
 
[1] pry(#<Book>)> title
=> "The Hobbit"

You’re now inside the method, with its arguments and instance state in scope.

Inspecting State Without puts

Every prompt above is a full pry REPL. You can read variables, call methods, and experiment with alternative implementations in the live frame. You can also add breakpoints from the console itself (covered in the breakpoints section), which avoids the "quit, move binding.pry, restart" loop.

Finishing a Debugging Session

finish runs the rest of the current frame and stops right after it returns. From inside initialize, it brings you back to the calling line:

Shell
[1] pry(#<Book>)> finish
 
From: app.rb:37 :
 
    35: book1 = Book.new("Dune", "Frank Herbert", 20.0)
    36: book2 = Book.new("The Hobbit", "J.R.R. Tolkien", 15.0)
 => 37: book3 = Book.new("Hobbit's Journey", "Unknown", 10.0)

continue resumes execution until the next breakpoint or the end of the program. !!! exits the debugger and the program immediately.

binding.pry only works where you can attach a console — once a bug ships, the backtrace has to come to you. AppSignal’s Ruby error tracking captures the full stack trace and request context in production, so you know exactly where to set your next breakpoint.

Advanced Use Cases of pry-byebug for Ruby

Now for a few techniques beyond plain stepping: navigating the stack, adding breakpoints on the fly, and conditional breakpoints.

Rewinding and Replaying

Can you move back and forth between two frames? Yes. After a step into initialize, up moves to the calling frame and down returns:

Shell
[1] pry(#<Book>)> up
 
From: app.rb:36 :
 
    35: book1 = Book.new("Dune", "Frank Herbert", 20.0)
 => 36: book2 = Book.new("The Hobbit", "J.R.R. Tolkien", 15.0)
    37: book3 = Book.new("Hobbit's Journey", "Unknown", 10.0)
 
[1] pry(main)> down
 
From: app.rb:8 Book#initialize:
 
     7: def initialize(title, author, price)
 =>  8:   @title = title

Neither command executes code; they only change which frame you inspect.

The backtrace command lists the frames in the current stack, with a cursor marking your position. One caveat from our Ruby 3.4 test run: calling backtrace inside a method reached through a C frame (such as Class#new) crashed byebug 13.0.0 with a NameError in its frame decoding. It works from plain Ruby frames. This is the kind of edge that byebug’s slowed maintenance leaves on new Rubies, and part of why debug is now the safer default.

Add Breakpoints On the Fly

To avoid restarting the program, add breakpoints from the debugger console with the break command. You can target a line in the current file, a line in another file, or the start of a method.

For example, add a breakpoint at the start of BookStore#add_book, then resume:

Shell
[1] pry(main)> break BookStore#add_book
 
  Breakpoint 1: BookStore#add_book (Enabled)
 
  19: def add_book(book)
  20:   @books << book
  21: end
 
[1] pry(main)> continue
 
  Breakpoint 1. First hit
 
From: app.rb:20 BookStore#add_book:
 
    19: def add_book(book)
 => 20:   @books << book
    21: end
 
[1] pry(#<BookStore>)> book.title
=> "Dune"

A bare break lists every breakpoint, numbered:

Shell
[1] pry(#<BookStore>)> break
 
  # Enabled At
  -------------
 
  1 Yes     BookStore#add_book

Delete one by number with break --delete 1, or disable them all with break --disable-all.

Conditional Breakpoints

Sometimes code only misbehaves for specific values. Instead of stopping at every call, attach a condition, and the debugger only stops when it’s truthy:

Shell
[1] pry(main)> break BookStore#add_book if book.title.match(/Hobbit/)
 
  Breakpoint 1: BookStore#add_book (Enabled) Condition: book.title.match(/Hobbit/)
 
[1] pry(main)> continue
 
  Breakpoint 1. First hit
 
From: app.rb:20 BookStore#add_book:
 
    19: def add_book(book)
 => 20:   @books << book
    21: end
 
[1] pry(#<BookStore>)> book.title
=> "The Hobbit"

The debugger skipped the add_book call for "Dune" and stopped at the first title matching the condition.

One gotcha from our tests on 3.12.0: write conditions with regexes, not string literals. The break command’s parsing stripped the quotes from book.title.include?("Hobbit"), so the condition raised on every call and the breakpoint never fired, without a warning. The regex form works reliably.

Wrapping Up

That should leave you few reasons to fall back on puts debugging in Ruby.

You can also configure pry-byebug through ~/.pryrc to add aliases and custom behaviors; see the pry-byebug README for details.

And when a bug only reproduces in production, pair your debugger with error monitoring. AppSignal’s exception handling guide for Ruby shows how to report rescued errors with Appsignal.report_error, so the failing context reaches you with the backtrace attached.

Happy coding!

P.S. If you’d like to read Ruby Magic posts as soon as they get off the press, subscribe to our Ruby Magic newsletter and never miss a single post!

P.P.S. Use AppSignal for Ruby for deeper debugging insights.

Frequently asked questions

Should I use pry-byebug or the debug gem?
Use the built-in debug gem on Ruby 3.1 or newer: it ships with Ruby and supports remote debugging through rdbg and VS Code. Choose pry-byebug when you want pry’s REPL features, like source browsing and runtime object inspection, at your breakpoints.
Is pry-byebug compatible with Ruby 3.4?
Yes. pry-byebug 3.12.0 installs and runs on Ruby 3.4, verified with binding.pry and all stepping commands. The gem requires Ruby 3.2 or newer, so projects on older Rubies must pin an earlier release.
What versions of pry and byebug does pry-byebug depend on?
pry-byebug 3.12.0 pins byebug to the 13.x series and pry to at least 0.13 and below 0.17. A fresh install on Ruby 3.4 resolves to byebug 13.0.0 and pry 0.16.0.
What is the difference between binding.pry and binding.break?
binding.pry opens a pry session and, with pry-byebug loaded, adds byebug stepping commands. binding.break comes from Ruby’s bundled debug gem and opens the rdbg console, which also powers remote debugging and the VS Code integration.

Published , Updated

Wondering what you can do next?

  • Share this article on social media
Thomas Riboulet

Thomas Riboulet

Our guest author Thomas is a Consultant Backend and Cloud Infrastructure Engineer based in France. For over 13 years, he has worked with startups and companies to scale their teams, products, and infrastructure. He has also been published several times in France's GNU/Linux magazine and on his blog.

All articles by Thomas Riboulet

Become our next author!

Find out more
$appsignal install

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal