# Debugging with Xdebug or PHP Debugger, by hand or with an AI

The JumPHP IDE has a real **step debugger**: you stop the program on a line, look at the variables, change one, go on step by step. It speaks **DBGp**, the protocol of Xdebug, so it can drive **two engines**:

- **Xdebug**, the classic one (the default);
- **[PHP Debugger](https://php-debugger.dev)**, a newer DBGp debugger, free and open source, **compatible with Xdebug's settings**, with near-zero overhead.

Both work the same way in the IDE. You can also hand the debugger to an **AI** (Claude Code): it sets the breakpoints, runs the program, reads the variables and finds the bug itself.

## Which engine to choose

| | Xdebug | PHP Debugger |
|---|---|---|
| In JumPHP | default, all runtimes | desktop runtime (PHP 8.2 to 8.5) |
| Overhead | PHP runs noticeably slower | "near-zero overhead when idle", always on, no trigger to set |
| Async runtime (PHP 8.6, `runtime: async`) | yes | **no**: Xdebug is used instead (`bin/jumphp` says so) |
| AI | the JumPHP MCP server (`bin/jumphp debug`) | its own MCP server (`dbgp mcp`), which also sees the script's output and PHP warnings |

In practice: **choose PHP Debugger** for everyday debugging, for long runs (a game that plays 600 frames, a test suite) and for AI debugging. **Keep Xdebug** if you work with the async runtime (threads), or if you need something only Xdebug does. The two are interchangeable in the IDE, so you can try both on the same project.

PHP Debugger's own page gives no benchmark figure: it only states the near-zero cost until you debug. Measure on your own project if the difference matters to you.

## Choose the engine in the IDE

- The **Debug** button in the header (the bug beside Run, `Shift+F9`) **unfolds a menu**: *Debug with* (Xdebug or PHP Debugger, for this run) and *Default engine* (the one a plain click uses from then on). A **right click** on the button opens the same menu.
- The first run with a given engine **builds a Docker image** once (about a minute). Later runs start at once.
- A notification tells you when the program never connects to the debugger (it ended first, or nothing after 3 minutes). The Run window's output usually says why.

## Choose the engine from a terminal

Everything goes through `bin/jumphp`, as always:

```bash
# Xdebug (default)
JUMPHP_XDEBUG=9003 bin/jumphp test pong

# PHP Debugger
JUMPHP_DEBUGGER=php-debugger JUMPHP_XDEBUG=9003 bin/jumphp test pong
```

- `JUMPHP_XDEBUG=<port>` turns step debugging on: PHP connects to a debugger listening on that port of your computer (composer, phpstan and `php -l` run without it).
- `JUMPHP_DEBUGGER=xdebug|php-debugger` picks the engine (default `xdebug`).
- With `php-debugger`, `bin/jumphp` builds the image `docker/desktop-phpdebugger` once, from the desktop image, which stays without it.
- On the async runtime (PHP 8.6) the message "PHP Debugger does not run on PHP 8.6 (async runtime): Xdebug is used" is shown and Xdebug runs.

## Debug by hand in the IDE

1. **Breakpoint**: click in the gutter, left of a line of PHP. A **right click** gives a **condition** (a PHP expression: it stops only when true, e.g. `$this->score < 0`); `Ctrl+F8` sets one at the caret. Breakpoints are kept per project and follow their lines as you edit.
2. **Debug** (`Shift+F9`): the program runs, and stops on the first breakpoint. The line is marked and the **Debug** tool window opens.
3. **Look**: the call stack (click a frame to see its variables), the variables as a tree read on demand, **watches**, and a field to **evaluate** any PHP expression where the program stopped.
4. **Change a value**: double click on a variable or `F2`, type a PHP expression (`9`, `"Ada"`, `[1, 2]`, `null`) and go on with the new value.
5. **Move**: Resume `F9`, Step Over `F8`, Step Into `F7`, Step Out `Shift+F8`, **Run to Cursor** `Alt+F9`, Stop.
6. **Exceptions**: **Stop at Exceptions…** (the warning icon of the Debug window): `*` or class names (`TypeError`, `App\GameOver`). It stops where the exception is thrown, caught or not, and shows which one with its message.

**Hover inspector** *(coming in the next IDE update)*: when the program is stopped at a breakpoint, move the mouse over a variable in the editor to get an **expandable tree** of its value, with **Copy** and **Edit**.

A tip for games: debug **headless runs** (`test`, `shot`): a game stopped inside a window freezes until you resume it.

## Debug with an AI

An AI debugs better with a debugger than with `var_dump()`: it reads the real values, one question at a time, and changes nothing in your code. Two MCP servers are available. Use **one** of them at a time (both listen on port 9003).

### 1. The JumPHP server: `bin/jumphp debug`

The IDE's own debugger as an MCP server (it runs in Docker, with Xdebug):

```bash
claude mcp add jumphp-debug -- bin/jumphp debug
```

Its tools:

| Tool | What it does |
|---|---|
| `debug_breakpoints` | sets a file's breakpoints (`lines`, or `{line, condition}`), lists them without a path, `clear_all` |
| `debug_exceptions` | the exceptions that stop the program (`*` = all, `[]` = none) |
| `debug_wait` | waits until the program stops at a breakpoint or an exception, then gives where, the stack and the variables |
| `debug_state` | where it is stopped, the stack, the variables of a frame |
| `debug_step` | `step_over`, `step_into`, `step_out`, `run`, `stop`: waits for the next stop |
| `debug_run_to` | runs to a line |
| `debug_eval` | evaluates a PHP expression where it stopped |
| `debug_variables` | a frame's variables, or the children of one (`$this->player`, `$list[3]`) |
| `debug_set_value` | gives a variable a new value (a PHP expression) |

Paths are relative to the project. Claude starts the program **itself**, in the background, with `JUMPHP_XDEBUG=9003`, then calls `debug_wait`.

### 2. PHP Debugger's server: `dbgp mcp`

PHP Debugger ships its own MCP server in a small program, `dbgp` (MIT license; Linux and macOS, amd64 and arm64; Windows amd64).

```bash
# install (macOS / Linux)
curl -fsSL https://github.com/php-debugger/dbgp-client/releases/latest/download/install.sh | sh
dbgp -version

# for this project / for all your projects
claude mcp add dbgp -- dbgp mcp
claude mcp add --scope user dbgp -- dbgp mcp
```

Inside Claude Code, `/mcp` shows that it is connected. Its tools, by their names in PHP Debugger's documentation:

- connection: `status`, `listen`, `unlisten`, `sessions`, `wait_for_session`, `detach`;
- breakpoints: `add_breakpoint`, `remove_breakpoint`, `breakpoints`;
- running: `continue`, `wait_for_stop`, `stop`;
- looking: `stack`, `variables`, `variable`, `variable_value`, `source`, `output`, `warnings`;
- acting: `eval`, `set_variable`;
- Docker / remote: `add_path_mapping`, `path_mappings`.

Useful flags (run `dbgp mcp -h` for all): `-php-port PORT` (the port PHP connects to, 9003 by default), `-mcp-port PORT`, `-idekey KEY`, `-map LOCAL=REMOTE` (a path mapping, repeatable), and `-no-eval`, which forbids `eval`, `set_variable` and conditional breakpoints: the AI can only look. It does **not** hide values, so passwords and keys stay visible to it.

Through `bin/jumphp`, `JUMPHP_DEBUGGER=php-debugger bin/jumphp debug` starts `dbgp mcp` for you (it needs `dbgp` installed on your computer). Then start the program with the same engine:

```bash
JUMPHP_DEBUGGER=php-debugger JUMPHP_XDEBUG=9003 bin/jumphp test pong
```

### A worked session

You, to Claude Code:

> The score of the `pong` game goes up by 2 instead of 1 when the ball passes the left paddle. **Use the debugger, not var_dump.** Set a breakpoint where the score is increased (look in `PlayScene`), run `bin/jumphp test pong` with `JUMPHP_XDEBUG=9003`, stop on it, read the variables, and tell me why. Fix it only after you have shown me the cause.

What the AI then does, with the tools above:

1. finds the line that adds a point and sets a breakpoint there, with a **condition** if the line runs on every frame (`$this->ballX < 0`);
2. starts the test in the background and waits for the stop (`debug_wait` / `wait_for_stop`);
3. reads `$this->score`, `$this->scored` and the stack to see who called this line;
4. steps out once (`debug_step` `step_out`) to see the second caller: the point is counted twice;
5. checks its idea without editing anything: it gives `$this->scored` a value (`debug_set_value` / `set_variable`) and continues to see the score stay right;
6. only then edits the code and runs the test again, with no breakpoint.

### Tips

- **Say it plainly**: "use the debugger". Otherwise an agent often falls back to `var_dump()`.
- Give it the command that shows the bug, what you expect, and where you suspect it.
- **Small steps**: a few `step_over`, then look. A **conditional breakpoint** (`$enemy->hp <= 0`) beats stepping through 600 frames.
- Let it read variables and evaluate expressions: no `var_dump()`, no `echo`, nothing to clean up after.
- Prefer headless runs (`test`, `shot`, `php`) to a window.
- **Secrets**: the AI sees every variable it reads (passwords, tokens, API keys). Debug with test data, and use `-no-eval` with `dbgp` if you do not want it to run code in your program.
- Do not leave the MCP server of a debugger connected in a project where you do not debug: remove it with `claude mcp remove`.

## Troubleshooting

**Nothing stops / "did not connect".**
Is a debugger listening on the port (the IDE's Debug window, or an MCP server)? The program must be started with `JUMPHP_XDEBUG=<port>` and the same engine as the listener.

**Port already in use.**
Two listeners on 9003: the IDE and `bin/jumphp debug` (or `dbgp mcp`), or two IDEs. Stop one, or use another port: `JUMPHP_DEBUG_PORT=9010 bin/jumphp debug` with `JUMPHP_XDEBUG=9010`, or `dbgp -php-port 9010 mcp`.

**The breakpoint is never hit.**
It is on a line without code (a comment, a blank line, a closing brace), or in a file the program never loads. Run `debug_state` / look at the stack to see where it is. With a `-map` or an IDE: the paths must match (below).

**Paths in Docker.**
`bin/jumphp` mounts your project at `/workspace` in the container: the debugger says `file:///workspace/...`. The IDE and `bin/jumphp debug` map this to the project's files by themselves, and the paths you give are **relative to the project**. With `dbgp mcp` alone, map the folders: `dbgp mcp -map /home/you/project=/workspace`.

**PHP 8.6 (async runtime).**
PHP Debugger does not run there: Xdebug is used. To use PHP Debugger, debug on the default runtime (PHP 8.5), or remove `runtime: async` for the session.

**Browser games.**
The debugger works on the **desktop** runtime. In the browser, reproduce the bug with `bin/jumphp test` or `shot` on the desktop.

See also [Coding, running and testing](/docs/ide-coder), [Claude in the IDE](/docs/ide-claude) and [IDE Remote](/docs/ide-remote).
