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:
| Task | Tool | Key Parameters |
|---|---|---|
| Get stack frames | debugger_frames | thread, start, end |
| Inspect locals | debugger_frame_locals | thread, frame |
| Evaluate in frame | debugger_eval_in_frame | thread, frame, code |
| List restarts | debugger_restarts | thread |
| Invoke restart | debugger_invoke_restart | thread, restart, args |
| Set breakpoint | breakpoint_set | function, condition, hit-count |
| Remove breakpoint | breakpoint_remove | breakpoint-id |
| Step execution | debugger_step | thread, mode |
| Continue | debugger_continue | thread |
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 levelUSE-VALUE: Provide a value to use insteadSTORE-VALUE: Store a value for future useRETRY: 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 callsover: Step over function callsout: 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_listto find other threads - Debug from a different thread
- Use
debugger_continueto 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 statebreakpoint_set- modifies running codethread_kill- terminates threads
Request approval before proceeding.
Best Practices
- Capture full context first: Get frames, locals, restarts before making changes
- Inspect before modifying: Understand state before intervening
- Use restarts when possible: Prefer restarts over frame manipulation
- Document findings: Note what caused the issue
- Fix the root cause: Use hot-reload tools after diagnosis
- 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