Skip to content

feat(xdebug): saved breakpoints for sessions without an IDE - #401

Merged
butschster merged 3 commits into
masterfrom
feat/xdebug-breakpoints
Nov 7, 2026
Merged

butschster merged 3 commits into
masterfrom
feat/xdebug-breakpoints

Conversation

@butschster

Copy link
Copy Markdown
Member

Part 3 of "Xdebug without an IDE" (parts 1 #392 and 2 #395 are merged): breakpoints that Buggregator keeps and sends to the sessions it drives.

What changes

  • Saved breakpoints in SQLite (xdebug_breakpoints, new migration): a line (file + line) or an exception class, optional condition, enabled, source (ui | mcp). They survive restarts.
  • Sent to every session Buggregator drives before the script runs. A change while a session is paused goes out at once (breakpoint_set / breakpoint_remove); while it runs, at its next pause. A session an IDE drives is not touched: the IDE sets its own.
  • REST (JSON only, cross-site refused):
    • GET /api/xdebug/breakpoints, POST /api/xdebug/breakpoints, PATCH /api/xdebug/breakpoints/{id}, DELETE /api/xdebug/breakpoints/{id}.
    • A condition is PHP code the engine runs: 403 eval_disabled without xdebug.allow_eval. A condition saved earlier while eval is now off is skipped (not sent at all).
  • Per-session state: GET /api/xdebug/sessions/{id}/breakpoints for a session driven here returns each saved breakpoint with state (resolved | unresolved | skipped | disabled | pending), resolved_line (Xdebug's breakpoint_resolved, e.g. a blank line moves to the next statement) and hit_count. For an IDE-driven session it still lists what the IDE set.
  • WS: breakpoints.changed (the full list), breakpoint.resolved (session_id, breakpoint_id, line).
  • Files to pick from: GET /api/xdebug/files?q=&limit=20, files seen in pauses and stacks (500 most recent, in memory) plus files of saved breakpoints; base-name/path prefix first, then substring.
  • MCP: xdebug_breakpoints (read, always on); xdebug_breakpoint_set, xdebug_breakpoint_remove (with allow_control).
  • waitSeconds clamps before multiplying (review note on feat(xdebug): MCP tools to drive a session without an IDE #395).

Tests

  • modules/xdebug/breakpoints_test.go (fake DBGp engine): sent before run, states incl. skipped/disabled, breakpoint_resolved → resolved_line, hit_count from breakpoint_list; add/disable/delete while paused; added while running → pending, sent at the next pause; REST validation (403/400/415/404); conditional command; SQLite persistence through the migration; files search and cap; MCP set/list/remove.
  • go test -race -count=1 ./modules/xdebug/ ./internal/app/ ./internal/storage/ ok; breakpoint tests -race -count=10 ok.
  • Live check with PHP 8.3 + Xdebug 3.4.5 in Docker: line breakpoint on a blank line resolved to the next line and hit; exception breakpoint on RuntimeException hit; breakpoints kept across a server restart (file DB); disabled ones not sent; a breakpoint added while paused hit on run.

Made with harnsy

- Table xdebug_breakpoints (line: file+line, or exception class; optional
  condition; enabled; source ui|mcp). Sent to every session Buggregator
  drives before the script runs; changes go out at once while paused, else
  at the next pause. An IDE keeps setting its own.
- REST: GET/POST /api/xdebug/breakpoints, PATCH/DELETE .../{id} (JSON only,
  cross-site refused). A condition is PHP code: 403 eval_disabled without
  allow_eval; one saved earlier is skipped (not sent) while eval is off.
- GET /api/xdebug/sessions/{id}/breakpoints for a session driven here: each
  saved breakpoint with state (resolved, unresolved, skipped, disabled,
  pending), resolved_line (breakpoint_resolved notify / breakpoint_list)
  and hit_count. WS: breakpoints.changed, breakpoint.resolved.
- GET /api/xdebug/files?q=&limit=: files seen in pauses and stacks (500
  most recent, in memory) plus the files of saved breakpoints.
- MCP: xdebug_breakpoints (read, always on); xdebug_breakpoint_set and
  xdebug_breakpoint_remove with allow_control.
- waitSeconds clamps before multiplying (no Duration overflow).
@butschster butschster added the type: feature Net-new functionality label Nov 7, 2026
- A breakpoint_remove the engine did not take (the script moved on) keeps
  the engine id and marks the session dirty; a deleted breakpoint is
  forgotten only after the engine removed it. The next pause retries.
- syncBreakpoints reads the current list after taking bpSyncMu, so a sync
  that waited never applies an older snapshot.
- BreakpointsChanged syncs before it returns: a paused session has the
  breakpoint before the caller can continue it. ownCallReply marks dirty
  under mu when refused, and on a timeout.
- Session state shows pending for a change not applied yet.
- MCP xdebug_breakpoints on an IDE-driven session lists the IDE's
  breakpoints; tool text says when a breakpoint reaches a session.
- File paths: absolute, file:// or Windows drive (C:\app\x.php →
  file:///C:/app/x.php); relative paths are refused.
@butschster
butschster merged commit a06ee03 into master Nov 7, 2026
1 check passed
@butschster
butschster deleted the feat/xdebug-breakpoints branch November 7, 2026 09:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feature Net-new functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant