Cannot enter pry prompt with dbg or IEx.pry()

Hello,
I am trying to pry some code and the request continues without entering pry(1)> prompt. I use require IEx; IEx.pry or dbg with iex --dbg pry -S mix phx.server . I can see request to pry in the terminal, but after clicking Y it just continues. It seems the same as in I cannot get IEx.pry() to work correctly, however I use Elixir 1.16.0 with OTP 26 (erlang 26.1.2). Also tested on 1.17-dev with the same result.

The problem has occurred in my project, but I also created a demo Phoenix app with mix phx.new demo and only changed a PageController to:

  def home(conn, _params) do
    # The home page is often custom made,
    # so skip the default app layout.

    conn
    |> foo()
    |> render(:home, layout: false)
  end

  defp foo(conn) do
    "Pry is not working?"
    |> String.split()
    |> Enum.count()
    |> dbg

    conn
  end

When running iex --dbg pry -S mix phx.server the pry console doesn’t show up. However on the other project with Elixir 1.14 it works (dbg was opt-out back then so I just use iex -S mix phx.server). What am I doing wrong?

Hi, welcome to Elixirforum!
I tried to reproduce this, but it worked for me.

Steps I tried:

  • Install Elixir 1.16.0-otp26 via asdf.
  • Install Erlang 26.1.2 via asdf.
  • Create project via mix phx.new demo --no-ecto. Did not want to set up the database. I have phx.new installer for Phoenix 1.7.10.
  • Added .tool-versions file to the root of demo project with relevant versions of Elixir and Erlang.
  • Installed dependencies.
  • Updated the code for lib/demo_web/controllers/page_controller.ex with your example.
  • Started the server with iex --dbg pry -S mix phx.server.
  • Visited http://localhost:4000, and reached a breakpoint. It worked for both dbg and require IEx; IEx.pry.

Tested on macOS Sonoma 14.4.1.

Can you try to reproduce the same issue on the other device?
Possibly via an Elixir Docker (Podman) container. If it works there, then possibly something is with your local environment.

I remember it was an similar issue with Elixir 1.15.0 and OTP 25, but it is already resolved.

1 Like

I tested in on the same machine (MacOS Ventura 13.6.6, MBP 2019 Intel based) with two different Elixir versions and it worked on 1.14.2-otp24, but didn’t on 1.16.0-otp26.
I pushed the code here: GitHub - onegrx/pry
If I change versions to

erlang 24.0.3
elixir 1.14.2-otp-24

and run iex -S mix phx.server the dbg causes stop of the execution and after pressing Y it goes into pry prompt:

Generated demo app
[info] Running DemoWeb.Endpoint with Bandit 1.4.1 at 127.0.0.1:4000 (http)
[info] Access DemoWeb.Endpoint at http://localhost:4000
Interactive Elixir (1.14.2) - press Ctrl+C to exit (type h() ENTER for help)
[watch] build finished, watching for changes...
iex(1)>
Rebuilding...

Done in 286ms.
[info] GET /
[debug] Processing with DemoWeb.PageController.home/2
  Parameters: %{}
  Pipelines: [:browser]
Request to pry #PID<0.6859.0> at DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:18)

   15:     |> String.split()
   16:     |> Enum.count()
   17:     |> IO.inspect()
   18:     |> dbg
   19:
   20:     conn
   21:   end

Allow? [Yn] y

Interactive Elixir (1.14.2) - press Ctrl+C to exit (type h() ENTER for help)
pry(1)>

But when the versions are:

erlang 26.1.2
elixir 1.16.0-otp-26

and run iex --dbg pry -S mix phx.server the execution also stops, but y is ignored:

Generated demo app
[info] Running DemoWeb.Endpoint with Bandit 1.4.1 at 127.0.0.1:4000 (http)
[info] Access DemoWeb.Endpoint at http://localhost:4000
Erlang/OTP 26 [erts-14.1.1] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [jit:ns]

[watch] build finished, watching for changes...
Interactive Elixir (1.16.0) - press Ctrl+C to exit (type h() ENTER for help)

Rebuilding...

Done in 310ms.
[info] GET /
[debug] Processing with DemoWeb.PageController.home/2
  Parameters: %{}
  Pipelines: [:browser]
Request to pry #PID<0.6020.0> at DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:18)

Request to pry #PID<0.6020.0> at DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:18)
   15:     |> String.split()
   16:     |> Enum.count()
   17:     |> IO.inspect()
   18:     |> dbg
   19:
   20:     conn
   21:   end
Allow? [Yn] y

Interactive Elixir (1.16.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> 

UPDATE: On erlang 26.1.2 + elixir 1.15.7-otp-26 it also doesn’t work for me, so the problem seems to appear always when dbg is opt-in with --dbg pry

Have tested your code on MacOS Sonoma 14.4.1 on MacMini 2018, Intel based.
It worked for me.

What I have tried:

  • Cloned a repo
  • Installed dependencies
  • Started the app with iex --dbg pry -S mix phx.server
  • Visited http://localhost:4000/ and reached a breakpoint.
  • Stopped the application.
  • Added another breakpoint (2) with require IEx; IEx.pry().
  • Started the application with iex -S mix phx.server and refreshed a page in browser.
  • Debugger stopped at breakpoint (2).

As I remember, --dbg pry argument appeared at Elixir 1.15.0. dbg macro does not stop at breakpoint without it (though it stopped in Elixir 1.14.x). I mainly use dbg for debug tracing in the code and require IEx; IEx.pry() to set breakpoints (as it always worked).

I think it is not related to the OS, but to the local environment.
Can you try to run your code inside the Docker/Podman?

  • Start an Elixir container, something like docker run --rm -it -p 127.0.0.1:4000:4000 elixir:1.16 bash
  • Install a git there apt update && apt install git
  • Clone your repo git clone https://github.com/onegrx/pry.git
  • Visit a repo and install dependencies cd pry; mix deps.get
  • Possibly you will want to comment a repo in your configuration. There is no Postgres inside a container. It is not relevant by the way.
  • Start the application via iex --dbg pry -S mix phx.server .
  • Visit http://localhost:4000 in your browser.

I have written previous commands just to show steps and have not tested them (have not installed Docker on my machine and deleted Podman some time ago). Possibly you will need to install extra dependencies (like PostgreSQL). It should be easy to find solutions for them in the Internet.

I have recently got an interesting error. I played with Erlang and added custom configuration file for it. I have printed a message inside it. When I installed a dependency (erlexec) - the installation process somehow took this message and tried to install dependencies through it. It failed, and I had to update my configuration files. Possibly something similar is in your case, so I suggested to try the same on the other machine.

Erlang looks for configuration in a couple of locations:

  • ~/.erlang
  • ~/Library/Application Support/erlang/.erlang
  • IEX uses ~/.iex.exs
  • Environment variable ERL_AFLAGS (you can see its contents via echo $ERL_AFLAGS). I have added this variable to store shell history.

If breakpoints will work inside the Docker/Podman container, then something is with your local configuration.

1 Like

You may also want to update a listening address in the config/dev.exs here inside a container to:

http: [ip: {0, 0, 0, 0}, port: 4000]

It will allow you to reach the server from outside the container.

Have tried to reproduce with Podman on Apple Silicon:

  • installed Podman (brew install podman).
  • Prepared a machine (podman machine init).
  • Started a machine (podman machine start).
  • Started the container with podman run --rm -it -p 127.0.0.1:4000:4000 elixir:1.16 bash. I use --rm flag here, so the container will be deleted after I log out. If you want to keep a container - just remove this flag.
  • Git is already installed, cloned the repo.
  • Tried to install the dependencies, but got this error, and applied the fix. It happens on Apple Silicon chips, so it should be fine on Intel-based one.
  • Installed the dependencies via mix deps.get.
  • Installed vim to edit files. You may use nano, if you are not familiar with vim (apt install nano).
  • Edited the config/dev.exs - allowed remote connections (ip: {0, 0, 0, 0} thing from above).
  • Commented Demo.Repo in lib/demo/application.ex (sole link from my previous message).
  • Started the application via iex --dbg pry -S mix phx.server.
  • Visited http://localhost:4000 in browser. Reached the breakpoint.
1 Like

Thank you for such a detailed answer. I’ve updated the repository, commented out Repo in configuration and allowed traffic from outside container by changing dev config ip to 0.0.0.0.
I started it with docker run --rm -it -p 4000:4000 elixir:1.16 bash, then cloned the repo, and started with iex --dbg pry -S mix phx.server and went to localhsot:4000. Execution stopped on dbg with request to pry, but after pressing y it continued to iex> prompt and didn’t go into pry> prompt even though I opted-in for it.
I also added simple function with require IEx; IEx.pry() and when I call it from the Docker iex it says the break is reached, but still doesn’t go into pry> prompt and waits to inspect local variables, but continues to run with a becoming available in iex!

iex(2)> DemoWeb.PageController.pry
Break reached: DemoWeb.PageController.pry/0 (lib/demo_web/controllers/page_controller.ex:27)

   24:     a = 10
   25:
   26:     require IEx
   27:     IEx.pry
   28:
   29:     b = 5
   30:     c = a + b

iex(3)> a
10

After this attempt I tried with Elixir 1.14.5
docker run --rm -it -p 4000:4000 elixir:1.14 bash
iex -S mix phx.server (since pry was opt-out back then)
and yet again it worked as expected stopping on dbg/pry and switching to pry(1)> prompt.

For accessing localhost:4000 when dbg is called in controller pipeline:

[info] GET /
[debug] Processing with DemoWeb.PageController.home/2
  Parameters: %{}
  Pipelines: [:browser]
Request to pry #PID<0.6843.0> at DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:18)

Request to pry #PID<0.6843.0> at DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:18)
   15:     |> String.split()
   16:     |> Enum.count()
   17:     |> IO.inspect()
   18:     |> dbg
   19:
   20:     conn
   21:   end
Allow? [Yn] Y

Interactive Elixir (1.14.5) - press Ctrl+C to exit (type h() ENTER for help)
pry(1)> n
"Pry is broken?" #=> "Pry is broken?"

Break reached: DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:15)

   12:
   13:   defp foo(conn) do
   14:     "Pry is broken?"
   15:     |> String.split()
   16:     |> Enum.count()
   17:     |> IO.inspect()
   18:     |> dbg

pry(1)> n
|> String.split() #=> ["Pry", "is", "broken?"]

Break reached: DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:16)

   13:   defp foo(conn) do
   14:     "Pry is broken?"
   15:     |> String.split()
   16:     |> Enum.count()
   17:     |> IO.inspect()
   18:     |> dbg
   19:

pry(1)> n
|> Enum.count() #=> 3

Break reached: DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:17)

   14:     "Pry is broken?"
   15:     |> String.split()
   16:     |> Enum.count()
   17:     |> IO.inspect()
   18:     |> dbg
   19:
   20:     conn

pry(1)> n
3
|> IO.inspect() #=> 3

[info] Sent 200 in 12415ms

For manually calling function with IEx.pry:

iex(3)> DemoWeb.PageController.pry
Break reached: DemoWeb.PageController.pry/0 (lib/demo_web/controllers/page_controller.ex:27)

   24:     a = 10
   25:
   26:     require IEx
   27:     IEx.pry
   28:
   29:     b = 5
   30:     c = a + b

pry(1)> n
{:ok, 15}

Interactive Elixir (1.14.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>

It seems it’s not issue with my local environment but different behaviour on newer Elixir versions. Also, I don’t have these files with Erlang configuration you have mentioned.

Here is my output:

Interactive Elixir (1.16.2) - press Ctrl+C to exit (type h() ENTER for help)

Rebuilding...

Done in 253ms.
[info] GET /
[debug] Processing with DemoWeb.PageController.home/2
  Parameters: %{}
  Pipelines: [:browser]
Request to pry #PID<0.664.0> at DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:18)

Request to pry #PID<0.664.0> at DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:18)
   15:     |> String.split()
   16:     |> Enum.count()
   17:     |> IO.inspect()
   18:     |> dbg
   19:
   20:     conn
   21:   end
Allow? [Yn] y

Interactive Elixir (1.16.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> whereami
Location: lib/demo_web/controllers/page_controller.ex:18

   16:     |> Enum.count()
   17:     |> IO.inspect()
   18:     |> dbg
   19:
   20:     conn

    (demo 0.1.0) lib/demo_web/controllers/page_controller.ex:18: DemoWeb.PageController.foo/1
    (demo 0.1.0) lib/demo_web/controllers/page_controller.ex:9: DemoWeb.PageController.home/2
    (demo 0.1.0) lib/demo_web/controllers/page_controller.ex:1: DemoWeb.PageController.action/2
    (demo 0.1.0) lib/demo_web/controllers/page_controller.ex:1: DemoWeb.PageController.phoenix_controller_pipeline/2
    (phoenix 1.7.11) lib/phoenix/router.ex:484: Phoenix.Router.__call__/5
    (demo 0.1.0) lib/demo_web/endpoint.ex:1: DemoWeb.Endpoint.plug_builder_call/2
    (demo 0.1.0) deps/plug/lib/plug/debugger.ex:136: DemoWeb.Endpoint."call (overridable 3)"/2
    (demo 0.1.0) lib/demo_web/endpoint.ex:1: DemoWeb.Endpoint.call/2
    (phoenix 1.7.11) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
    (bandit 1.4.1) lib/bandit/pipeline.ex:150: Bandit.Pipeline.call_plug!/2
    (bandit 1.4.1) lib/bandit/pipeline.ex:36: Bandit.Pipeline.run/4
    (bandit 1.4.1) lib/bandit/http1/handler.ex:12: Bandit.HTTP1.Handler.handle_data/3
    (bandit 1.4.1) lib/bandit/delegating_handler.ex:18: Bandit.DelegatingHandler.handle_data/3
    (bandit 1.4.1) /pry/deps/thousand_island/lib/thousand_island/handler.ex:411: Bandit.DelegatingHandler.handle_continue/2
    (stdlib 5.2.1) gen_server.erl:1085: :gen_server.try_handle_continue/3
    (stdlib 5.2.1) gen_server.erl:995: :gen_server.loop/7
    (stdlib 5.2.1) proc_lib.erl:241: :proc_lib.init_p_do_apply/3

iex(2)>

Have taken it from container:

  • Started the application
  • Visited http://localhost:4000 in browser.
  • Reached a breakpoint, pressed Y
  • got into debugger. The debugger did not change the prompt to pry>, but kept iex> one.
  • I was able to run debugging-related commands (for example whereami) in the context of debugged process, though the prompt is still iex>.

Great, that it is not the case.

I see from your example, that you have reached the breakpoint in Elixir 1.16 too. Can you enter debugger-related commands from there (for example whereami)? If yes, then your debugger is working.

Do I understand correctly, that you expected to see the pry> prompt instead of iex> there (and thought pry did not work)?

1 Like

Possibly newer versions of Elixir using uniform interface for both pry and iex shells (or even using the same code for both cases). I was not aware of this, and even did not notice it. I agree that it is nice to have a separate prompt for debugger - it helps to distinguish a mode, and less confusing.
But it is not critical for me, as long as debugging-related features work.

It seems that Elixir 1.14 had a prefix for pry. But starting from Elixir 1.15.0, pry does not change it, and uses a default one. The same is with newer versions. The pry prefix was removed in this commit. The part of commit description mentions pry(N) will now be styled as iex(N).

1 Like

Yes, I expected to see the pry> prompt to know that the execution is stopped and inside pry context so there are debug functions available (IEx — IEx v1.16.2). Now it happens that indeed prompt doesn’t change so you should be aware of where you are and you have now these functions available directly in iex (even when you just start fresh iex you’ll see different output for typing a - undefined variable, and for n which calls n/0 IEx.Helpers — IEx v1.16.2 - but nothing happens if there were no breakpoints.

iex(9)>
nil
[info] GET /
[debug] Processing with DemoWeb.PageController.home/2
  Parameters: %{}
  Pipelines: [:browser]
Request to pry #PID<0.6065.0> at DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:18)

Request to pry #PID<0.6065.0> at DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:18)
   15:     |> String.split()
   16:     |> Enum.count()
   17:     |> IO.inspect()
   18:     |> dbg
   19:
   20:     conn
   21:   end
Allow? [Yn] y

Interactive Elixir (1.16.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(10)> n
"Pry is broken?" #=> "Pry is broken?"

Break reached: DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:15)

   12:
   13:   defp foo(conn) do
   14:     "Pry is broken?"
   15:     |> String.split()
   16:     |> Enum.count()
   17:     |> IO.inspect()
   18:     |> dbg

iex(11)> n
|> String.split() #=> ["Pry", "is", "broken?"]

Break reached: DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:16)

   13:   defp foo(conn) do
   14:     "Pry is broken?"
   15:     |> String.split()
   16:     |> Enum.count()
   17:     |> IO.inspect()
   18:     |> dbg
   19:

iex(12)> n
|> Enum.count() #=> 3

Break reached: DemoWeb.PageController.foo/1 (lib/demo_web/controllers/page_controller.ex:17)

   14:     "Pry is broken?"
   15:     |> String.split()
   16:     |> Enum.count()
   17:     |> IO.inspect()
   18:     |> dbg
   19:
   20:     conn

iex(13)> n
3
|> IO.inspect() #=> 3

[info] Sent 200 in 16423ms
** (EXIT from #PID<0.6065.0>) shell process exited with reason: shutdown: :timeout

Interactive Elixir (1.16.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(13)> n

Interactive Elixir (1.16.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(13)>

That was confusing because when I last used dbg/pry I did it on 1.14 and the prompt was changed. Actually if one reads these docs Debugging — Elixir v1.16.2 carefully without prior experience there is no error, but the attached asciinema shows behaviour on 1.14 when the prompt was different. Why was this change made?

Also, thank you very much for this effort to investigate and help!

1 Like

Glad that debugger works for you! You are very attentive to details.

Please, @josevalim , can you help us with this question?

It seems, that debugger prompt in was pry> inside the debugger (before Elixir 1.15.0). It allowed to distinguish between iex shell and debugging session. This distinction was removed in this commit:

It seems, that the asciinema in docs for newer versions uses old prefix (pry>):

Was it intended to make the iex access more uniform, or different prompts lead to bugs? I am happy with the way IEx works, and even did not notice the difference at first. Just curious about the intention.

1 Like

Recent Erlang versions allow multiline prompts but, to enable it, we no longer can change the prompt on the fly, hence the change.

2 Likes

Thanks for the explanation.

I have recently played with Erlang and noticed that it does not have a CLI debugger, like IEx.pry. There are some external libraries, but the most recent one does not save evaluation history due to the changes in io:get_line/1 (since OTP 26+). It would be great to have a tool to debug Erlang too.

I have tried debugger, but it does not have completion in Eval box. Besides I am more used to CLI debugging.

How do you think, is it a nice feature for the Erlang core? Possibly there will be ways to use Elixir (or other BEAM-based language) together with Debugger CLI or Debugger UI. For example, each language can implement a debugging behaviour (hooks for completion, formatting output, handling custom syntax, action on breakpoint, etc).

I like the idea, that you can switch between debugged processes in Debugger UI. Besides it could be useful to have a default action on breakpoint - a process can wait in background in suspended state, ask a user about decision, or continue if another process already reached the same breakpoint).

Thinking about the available options for this (contribute to Erlang/OTP, create a simple debugging script just for myself, create a library, try reaching existing library and contribute to it).

It depends on what you want to achieve. If you want to debug Elixir, the best would be to write a debugger for Elixir, because the debugger should be as close as possible to its target language, otherwise you are debugging the expansion of Elixir to Erlang which adds some layers between, affecting DX.

If you want to provide an Erlang debugger that also works in the shell, extending its debugger must be doable, and probably a worthy first step anyway.

1 Like

Thank you for the answer.

I wanted to port a CRDT library to BEAM VM. It should allow to sync data between different tech stacks and provide offline-first features. But needed to test if it will work fine with immutable structures. It should be a side project, so I wanted to learn Erlang through writing a POC implementation.

I do a debugger-driven development (at least if language allows it). I write code in shell and debug it there. But I was not able to find a CLI debugger for Erlang. There are some libraries, but I was not able to fit them properly into my workflow (missing shell history and lack of auto-completion would decrease my productivity).

I like my iex workflow in Elixir - it is a game-changer. And I miss similar tool for Erlang. I can find how each feature is implemented in OTP :debugger module, so writing a basic CLI should not be a problem. Possibly it is only me who misses CLI debugger.

I digged into how debugger works in Elixir. Possibly Erlang CLI debugger will not give much benefits for it, but I may not be aware of everything. Just wanted to hear your opinion.

Planned to create a simple POC of debugger, just to be more productive with Erlang. I could reach the maintained debugger library with proposals (but it would be rewriting a large part of existing library).

So my main question if it can be useful to Elixir (and other languages in BEAM ecosystem) to have an extensible CLI debugger, or this thing is not worth the effort. I am aware, that there are LSP for both Elixir and Erlang that support DAP protocol and allow debugging in the IDE.