SBCL Debugging Workflows

February 11, 2026 ยท View on GitHub

This guide provides workflows for debugging Common Lisp applications using the SBCL Debugging MCP tools.

Quick Reference

Debugging Tool Cheat Sheet:

TaskToolKey Parameters
Get stack framesdebugger_framesthread, start, end
Inspect localsdebugger_frame_localsthread, frame
Evaluate in framedebugger_eval_in_framethread, frame, code
List restartsdebugger_restartsthread
Invoke restartdebugger_invoke_restartthread, restart, args
Set breakpointbreakpoint_setfunction, condition, hit-count
Remove breakpointbreakpoint_removebreakpoint-id
Step executiondebugger_stepthread, mode
Continuedebugger_continuethread

Core Debugging Philosophy

Common Lisp's debugger is fundamentally different from traditional debuggers:

  • Condition System: Errors raise conditions, not crash immediately
  • Restarts: Users choose how to recover from errors
  • Interactive: Debugger is a REPL with full Lisp power
  • Non-intrusive: Debugging doesn't require special compilation

Workflow 1: Post-Mortem Debugging

When an error occurs, capture and analyze the context.

Step 1: Capture Error Context

{
  "tool": "debugger_frames",
  "arguments": {
    "thread": "auto"
  }
}

This returns:

  • Frame count and details
  • Function names for each frame
  • Source locations (file, line, column)
  • For each frame: local variables with values and object IDs

Step 2: Inspect Error Condition

{
  "tool": "debugger_restarts",
  "arguments": {
    "thread": "auto"
  }
}

Returns available restarts:

  • ABORT: Exit debugger, return to top level
  • USE-VALUE: Provide a value to use instead
  • STORE-VALUE: Store a value for future use
  • RETRY: Retry the operation

Step 3: Examine Local Variables

For frame 0 (error location):

{
  "tool": "debugger_frame_locals",
  "arguments": {
    "frame": 0
  }
}

For non-primitive values, use the returned object_id:

{
  "tool": "inspect_object",
  "arguments": {
    "id": 42,
    "max_depth": 3
  }
}

Step 4: Evaluate in Frame Context

{
  "tool": "debugger_eval_in_frame",
  "arguments": {
    "frame": 0,
    "code": "(type-of x)"
  }
}

This evaluates in the lexical environment of that frame.

Step 5: Invoke Restart to Recover

{
  "tool": "debugger_invoke_restart",
  "arguments": {
    "restart": "USE-VALUE",
    "frame": 0,
    "args": ["new-value"]
  }
}

Workflow 2: Live Debugging

Attach debugger to a running process or set breakpoints proactively.

Step 1: Start Debug Server

;; In the running Lisp image
(ql:quickload :cl-tron-mcp)
(cl-tron-mcp:start-server :transport :stdio)

Step 2: List Threads

{
  "tool": "thread_list"
}

Returns all threads with their states.

Step 3: Attach Debugger to Thread

{
  "tool": "thread_debug",
  "arguments": {
    "thread-id": "T1-12345"
  }
}

Step 4: Set Breakpoint

{
  "tool": "breakpoint_set",
  "arguments": {
    "function": "my-package:process-data",
    "condition": "(> x 100)"
  }
}

Returns breakpoint ID for later removal.

Step 5: Step Through Code

{
  "tool": "debugger_step",
  "arguments": {
    "thread": "auto",
    "mode": "into"
  }
}

Modes:

  • into: Step into function calls
  • over: Step over function calls
  • out: Step out of current function

Step 6: Continue Execution

{
  "tool": "debugger_continue",
  "arguments": {
    "thread": "auto"
  }
}

Workflow 3: Interactive Object Inspection

Deep inspection of Lisp objects during debugging.

Basic Inspection

{
  "tool": "inspect_object",
  "arguments": {
    "id": 42
  }
}

Returns object type and all slots/elements.

Inspect CLOS Instance

{
  "tool": "inspect_slot",
  "arguments": {
    "object-id": 42,
    "slot-name": "cache"
  }
}

Inspect Function

{
  "tool": "inspect_function",
  "arguments": {
    "symbol": "my-package:compute"
  }
}

Returns:

  • Function name
  • Lambda list
  • Documentation string
  • Closure variables (if applicable)

Inspect Package

{
  "tool": "inspect_package",
  "arguments": {
    "package": "MY-APP"
  }
}

Returns:

  • Package nicknames
  • Use list
  • Export list
  • Internal symbols

Workflow 4: Conditional Debugging

Set up complex debugging scenarios.

Break with Condition

{
  "tool": "breakpoint_set",
  "arguments": {
    "function": "my-app:handle-request",
    "condition": "(equal (car args) \"admin\")"
  }
}

Break on Hit Count

{
  "tool": "breakpoint_set",
  "arguments": {
    "function": "my-app:process-item",
    "hit-count": 10
  }
}

Break on Thread

{
  "tool": "breakpoint_set",
  "arguments": {
    "function": "my-app:worker",
    "thread": "worker-1"
  }
}

Workflow 5: Recovery and Continuation

After debugging, continue execution safely.

Use Restart to Continue

{
  "tool": "debugger_invoke_restart",
  "arguments": {
    "restart": "CONTINUE"
  }
}

Return Value from Frame

{
  "tool": "debugger_return_from_frame",
  "arguments": {
    "frame": 5,
    "value": "(list result)"
  }
}

Fix and Retry

{
  "tool": "debugger_eval_in_frame",
  "arguments": {
    "frame": 0,
    "code": "(setf x 10)"
  }
}

Then invoke RETRY restart.

Common Scenarios

Scenario: Null Pointer Equivalent

Lisp uses NIL which can mean "not found" or "no value":

{
  "tool": "debugger_eval_in_frame",
  "arguments": {
    "frame": 0,
    "code": "(type-of problematic-var)"
  }
}

Check if it's:

  • NIL (false/no value)
  • Empty list ()
  • Unbound slot (error condition)

Scenario: Type Error

{
  "tool": "debugger_eval_in_frame",
  "arguments": {
    "frame": 0,
    "code": "(list (type-of x) x)"
  }
}

Examine actual types and values.

Scenario: Infinite Loop

Set breakpoint in suspected loop:

{
  "tool": "breakpoint_set",
  "arguments": {
    "function": "my-app:loop-body",
    "hit-count": 1
  }
}

Then use debugger_step to examine each iteration.

Scenario: Memory Issues

{
  "tool": "memory_stats"
}

Check generation sizes and GC frequency.

Error Handling

Debugger Already Active

If debugger is already active in a thread:

  • Use thread_list to find other threads
  • Debug from a different thread
  • Use debugger_continue to exit first

No Error Context

If no error occurred but you want to inspect:

{
  "tool": "debugger_frames",
  "arguments": {
    "thread": "current"
  }
}

Permission Denied

Some operations require approval:

  • debugger_eval_in_frame - can modify state
  • breakpoint_set - modifies running code
  • thread_kill - terminates threads

Request approval before proceeding.

Best Practices

  1. Capture full context first: Get frames, locals, restarts before making changes
  2. Inspect before modifying: Understand state before intervening
  3. Use restarts when possible: Prefer restarts over frame manipulation
  4. Document findings: Note what caused the issue
  5. Fix the root cause: Use hot-reload tools after diagnosis
  6. Verify the fix: Re-run to confirm the issue is resolved

See Also

  • @prompts/hot-reload-development.md - Fixing code while running
  • @agents/sbcl-debugging-expert.md - Deep debugging strategies
  • docs/tools/debugger.md - Complete debugger tool reference