Skip to content

feat: debugger execution lifecycle#9970

Draft
dmadisetti wants to merge 2 commits into
mainfrom
dm/debugger
Draft

feat: debugger execution lifecycle#9970
dmadisetti wants to merge 2 commits into
mainfrom
dm/debugger

Conversation

@dmadisetti

@dmadisetti dmadisetti commented Jun 23, 2026

Copy link
Copy Markdown
Member

📝 Summary

Adds an experimental debugger execution life cycle that adds frame watching to execution. This in turn is able to highlight executing lines, and allow debug points.

Closes #8335

@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Jun 23, 2026 5:44pm

Request Review

@github-actions github-actions Bot added the bash-focus Area to focus on during release bug bash label Jun 23, 2026
@dmadisetti dmadisetti changed the title Dm/debugger feat: debugger execution lifecycle Jun 23, 2026
@dmadisetti dmadisetti added the enhancement New feature or request label Jun 23, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 48 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="frontend/src/core/codemirror/cells/debugger-decorations.ts">

<violation number="1" location="frontend/src/core/codemirror/cells/debugger-decorations.ts:141">
P2: Initial breakpoint sync captures a stale snapshot and can race with newer updates. This can briefly restore removed/old breakpoint markers after mount.</violation>
</file>

<file name="frontend/src/components/editor/output/MarimoTracebackOutput.tsx">

<violation number="1" location="frontend/src/components/editor/output/MarimoTracebackOutput.tsx:231">
P1: Debugger breakpoint toggle can throw in static notebooks. Guard the new toggle path with `!isStaticNotebook()` before calling `toggleBreakpoint`.</violation>
</file>

<file name="marimo/_runtime/runtime.py">

<violation number="1" location="marimo/_runtime/runtime.py:691">
P1: Out-of-band dispatch can delay breakpoint updates behind code completion. During a running cell, this can miss intended stops because breakpoints are applied too late.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant UI as Frontend (Browser)
    participant Editor as CodeMirror Editor
    participant WS as WebSocket / Kernel Connection
    participant OOB as Out‑of‑Band Worker
    participant Kernel as Kernel (Runtime)
    participant FW as FrameWatcher (sys.settrace)
    participant Pdb as MarimoPdb

    Note over UI,Pdb: NEW: Live Debugger Lifecycle

    UI->>Editor: User toggles gutter breakpoint
    Editor->>Editor: toggleBreakpoint(cellId, line)
    Editor->>UI: update breakpointsAtom → sendSetBreakpoints
    UI->>WS: POST /api/kernel/pdb/breakpoints
    WS->>OOB: enqueue SetBreakpointsCommand
    OOB->>Kernel: dispatch_out_of_band(SetBreakpointsCommand)
    Kernel->>Pdb: update self.breakpoints[cellId]

    Note over UI,Pdb: Cell Execution with Debugger

    UI->>WS: User runs cell
    WS->>Kernel: control queue → ExecuteCellCommand
    Kernel->>Kernel: Runner creates DebuggerLifecycle (if flag enabled)
    Kernel->>FW: setup() → install()
    FW->>FW: sys.settrace(self._trace)
    FW->>FW: start heartbeat thread (75ms interval)
    Kernel->>Kernel: execute cell body (Python code)
    Note over FW: trace fires on every line event
    
    loop Every line
        FW->>FW: record (cellId, line) as current
        alt Line matches a breakpoint
            FW->>Pdb: interaction(frame, traceback)
            Pdb-->>UI: stdin message (Pdb prompt)
            UI->>Pdb: user sends pdb command (e.g. c, n, s)
            alt User quits (q)
                Pdb->>FW: quitting flag set
                FW->>Kernel: raise MarimoStopError
                Kernel->>FW: teardown() → uninstall()
            end
        else No breakpoint
            FW->>FW: continue tracing
        end
    end

    loop Heartbeat (75ms)
        FW-->>WS: DebuggerLineNotification (cellId, line)
        WS->>UI: handleMessage("debugger-line")
        UI->>UI: update debuggerCurrentLineAtom
        UI->>Editor: highlight current line (cm-debugger-current-line)
    end

    Note over UI,Pdb: Cell finishes or is interrupted

    Kernel->>FW: teardown() → uninstall()
    FW->>FW: sys.settrace(prev), stop heartbeat
    alt Cell became idle
        FW->>WS: DebuggerLineNotification (cellId, line=None)
        WS->>UI: clear highlight
    end
    Kernel->>UI: status="idle"
    UI->>UI: resolve dangling stdin prompt, debuggerActive=false

    Note over UI,Pdb: Clearing cell (trashcan)

    UI->>Editor: onClear()
    Editor->>Editor: clearCellBreakpoints(cellId)
    Editor->>WS: POST /api/kernel/pdb/breakpoints (empty map)
    WS->>OOB: SetBreakpointsCommand (no breakpoints)
    OOB->>Kernel: dispatch_out_of_band
    Kernel->>Pdb: clear breakpoints[cellId]
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment on lines +231 to +233
if (debuggerEnabled) {
toggleBreakpoint(info.cellId, info.lineNumber);
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Debugger breakpoint toggle can throw in static notebooks. Guard the new toggle path with !isStaticNotebook() before calling toggleBreakpoint.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/editor/output/MarimoTracebackOutput.tsx, line 231:

<comment>Debugger breakpoint toggle can throw in static notebooks. Guard the new toggle path with `!isStaticNotebook()` before calling `toggleBreakpoint`.</comment>

<file context>
@@ -219,6 +228,10 @@ export const replaceTracebackFilenames = (domNode: DOMNode) => {
               >
                 <BugPlayIcon
                   onClick={() => {
+                    if (debuggerEnabled) {
+                      toggleBreakpoint(info.cellId, info.lineNumber);
+                      return;
</file context>
Suggested change
if (debuggerEnabled) {
toggleBreakpoint(info.cellId, info.lineNumber);
return;
if (debuggerEnabled && !isStaticNotebook()) {
toggleBreakpoint(info.cellId, info.lineNumber);
return;
}

self.code_completion(request, docstrings_limit=80)
# Block for the next command, then drain and dispatch whatever
# else is queued in one pass (latest of each type wins).
for command in collapse_out_of_band(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Out-of-band dispatch can delay breakpoint updates behind code completion. During a running cell, this can miss intended stops because breakpoints are applied too late.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At marimo/_runtime/runtime.py, line 691:

<comment>Out-of-band dispatch can delay breakpoint updates behind code completion. During a running cell, this can miss intended stops because breakpoints are applied too late.</comment>

<file context>
@@ -663,30 +665,62 @@ def globals(self) -> dict[Any, Any]:
-                self.code_completion(request, docstrings_limit=80)
+                # Block for the next command, then drain and dispatch whatever
+                # else is queued in one pass (latest of each type wins).
+                for command in collapse_out_of_band(
+                    out_of_band_queue, first=out_of_band_queue.get()
+                ):
</file context>

// sync (e.g. for editors that mount after breakpoints already exist).
const initial = observable.get();
if (initial.size > 0) {
queueMicrotask(() => apply(initial));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Initial breakpoint sync captures a stale snapshot and can race with newer updates. This can briefly restore removed/old breakpoint markers after mount.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/core/codemirror/cells/debugger-decorations.ts, line 141:

<comment>Initial breakpoint sync captures a stale snapshot and can race with newer updates. This can briefly restore removed/old breakpoint markers after mount.</comment>

<file context>
@@ -0,0 +1,187 @@
+    // sync (e.g. for editors that mount after breakpoints already exist).
+    const initial = observable.get();
+    if (initial.size > 0) {
+      queueMicrotask(() => apply(initial));
+    }
+    this.unsubscribe = observable.sub(apply);
</file context>

Comment thread packages/openapi/api.yaml
- cellId
title: DebugCellRequest
type: object
DebuggerLineNotification:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe ActiveLineNotification

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an experimental “debugger execution lifecycle” to marimo, enabling live per-line execution indication and session-scoped breakpoints that can apply while a cell is running (Colab-like current-line tracking + gutter breakpoints + pdb integration).

Changes:

  • Introduces DebuggerLifecycle/FrameWatcher to trace executing cell frames and emit debugger-line notifications + enter pdb on breakpoints.
  • Adds a new out-of-band command path (OutOfBandCommand) and worker to process completions + breakpoint updates off the main control loop.
  • Adds API + frontend plumbing for session breakpoints (/api/kernel/pdb/breakpoints) and UI elements (breakpoint gutter + live line highlight + updated debugger controls).

Reviewed changes

Copilot reviewed 48 out of 48 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/_session/test_queue.py Verifies breakpoint commands are routed to the off-main-loop queue.
tests/_runtime/test_request_router.py Updates NOT_ROUTED surface to include breakpoint updates.
tests/_runtime/test_kernel_lifecycle.py Adds tests for out-of-band command collapsing behavior.
tests/_runtime/test_executor_debugger.py New tests for FrameWatcher tracing, breakpoints, stepping, quitting, and notifications.
tests/_runtime/runner/test_cell_runner.py Tests that debugger lifecycle is gated by experimental flag and debugger presence.
packages/openapi/src/api.ts Updates generated OpenAPI TS types for new endpoint + schemas.
packages/openapi/api.yaml Adds endpoint + schemas for breakpoints and debugger-line notification.
marimo/_smoke_tests/debugger_test.py Adds a manual smoke-test notebook for the debugger lifecycle UX.
marimo/_session/types.py Widens completion queue type to OutOfBandCommand.
marimo/_session/queue.py Routes out-of-band commands to the separate queue.
marimo/_session/managers/queue.py Stores out-of-band queue instead of completion-only queue.
marimo/_session/managers/ipc.py Updates IPC manager typing for out-of-band queue.
marimo/_session/managers/app_host.py Updates app-host push queue typing for out-of-band queue.
marimo/_server/models/models.py Adds request model for set-breakpoints mapped to command.
marimo/_server/api/endpoints/execution.py Adds /pdb/breakpoints endpoint to dispatch breakpoint updates.
marimo/_runtime/runtime.py Adds out-of-band worker + dispatch + kernel-side breakpoint update handler.
marimo/_runtime/runner/cell_runner.py Installs DebuggerLifecycle when experimental flag is enabled.
marimo/_runtime/marimo_pdb.py Adds session-scoped breakpoints store + SIGINT disable helper.
marimo/_runtime/kernel_lifecycle.py Adds collapse_out_of_band queue-draining helper.
marimo/_runtime/executor/lifecycles/debugger.py Implements FrameWatcher + DebuggerLifecycle.
marimo/_runtime/executor/init.py Exports DebuggerLifecycle.
marimo/_runtime/commands.py Adds SetBreakpointsCommand + OutOfBandCommand union.
marimo/_pyodide/pyodide_session.py Mirrors out-of-band queue handling in pyodide runtime.
marimo/_messaging/notification.py Adds DebuggerLineNotification.
marimo/_ipc/queue_manager.py Updates IPC queue types/docs for out-of-band queue.
marimo/_ipc/connection.py Updates IPC completion channel to decode OutOfBandCommand.
marimo/_config/config.py Adds experimental.debugger config flag.
marimo/_cli/development/commands.py Ensures new schemas/notifications are included in API schema generation.
frontend/src/core/websocket/useMarimoKernelConnection.tsx Handles debugger-line notifications to update UI state.
frontend/src/core/wasm/bridge.ts Adds stub for sendSetBreakpoints in wasm bridge.
frontend/src/core/network/types.ts Adds SetBreakpointsRequest and sendSetBreakpoints to request interface.
frontend/src/core/network/requests-toasting.tsx Adds no-toast handling for sendSetBreakpoints.
frontend/src/core/network/requests-static.ts Adds sendSetBreakpoints to static-mode request client.
frontend/src/core/network/requests-network.ts Implements network request for /api/kernel/pdb/breakpoints.
frontend/src/core/network/requests-lazy.ts Registers lazy action for sendSetBreakpoints.
frontend/src/core/islands/bridge.ts Adds stub for sendSetBreakpoints in islands bridge.
frontend/src/core/islands/bootstrap.ts Allows debugger-line messages through islands message handler.
frontend/src/core/config/feature-flag.tsx Adds experimental.debugger feature flag.
frontend/src/core/codemirror/cells/extensions.ts Adds debugger gutter + live-line highlighter extensions behind flag.
frontend/src/core/codemirror/cells/debugger-state.ts Adds breakpoint/current-line atoms and breakpoint sync to kernel.
frontend/src/core/codemirror/cells/debugger-decorations.ts Implements CodeMirror gutter markers + current-line decoration.
frontend/src/core/cells/cell.ts Clears stdin/pdb prompt state when cell finishes (idle) as well as interrupt.
frontend/src/core/cells/tests/cell.test.ts Tests stdin/pdb prompt resolution on idle transition.
frontend/src/components/editor/output/MarimoTracebackOutput.tsx Bug icon toggles real gutter breakpoint when debugger flag enabled.
frontend/src/components/editor/notebook-cell.tsx “Clear” also clears cell breakpoints to keep UI/kernel in sync.
frontend/src/components/debugger/debugger-code.tsx “Clear” now quits pdb first, then clears console output.
frontend/src/components/app-config/user-config-form.tsx Adds UI checkbox for experimental.debugger with description.
frontend/src/mocks/requests.ts Adds mock implementation for sendSetBreakpoints.

Comment on lines +707 to +711
if isinstance(command, SetBreakpointsCommand):
self.set_breakpoints(command)
elif isinstance(command, CodeCompletionCommand):
self.code_completion(command, docstrings_limit=docstrings_limit)

Comment on lines +45 to +53
function sendBreakpoints(map: ReadonlyMap<CellId, ReadonlySet<number>>): void {
const breakpoints: Record<string, number[]> = {};
for (const [cellId, lines] of map) {
if (lines.size > 0) {
breakpoints[cellId] = [...lines].toSorted((a, b) => a - b);
}
}
void getRequestClient().sendSetBreakpoints({ breakpoints });
}
Comment on lines +101 to +110
@app.cell
def _():
# large line in gutter
# large line in gutter
# large line in gutter
# large line in gutter
# large line in gutter
# large line in gutter
# large line in gutter
# large line in gutter
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bash-focus Area to focus on during release bug bash enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: show current line execution indicator in notebook cells (similar to Google Colab)

3 participants