WORKSHOP
Multi-Stack Debug Mastery
Orchestrating VS Code for Python + Frontend Development
1 / 64
INTRODUCTION
The Debugging Challenge
In the era of microservices and full-stack applications, debugging in isolation is rarely enough.
Multiple terminal tabs for FastAPI, Vite, Electron
Context-switch fatigue slows down development
No unified debugging experience
Manual process management = wasted time
2 / 64
╔══════════════════════════════════════╗
"The best debugging tool is still careful thought, coupled with well-designed code."
╚══════════════════════════════════════╝
— Brian Kernighan
3 / 64
Workshop Timeline (90 minutes)
Intro → Part 1 → Part 2 → Part 3 → Part 4 → Q&A
4 / 64
LEARNING OBJECTIVES
What You'll Master
By the end of this workshop, you'll be able to:
01
Python Debug Configs
Configure launch.json for files, modules, pytest
VS Code Python
02
Frontend Debug
Debug JavaScript/TypeScript in Chrome and Electron
Chrome DevTools
03
Debug Controls
Master breakpoints, stepping, and debugging actions
Controls F5/F10/F11
04
Variable Viewer
Inspect locals, globals, and watch expressions
Variables Watch
05
Orchestrate Tasks
Automate services with tasks.json
Tasks Automation
06
Remote Attach
Debug Python apps on specific ports with debugpy
debugpy Attach
5 / 64
BASICS
Python Debugging Setup
launch.json Configuration
6 / 64
PYTHON DEBUGGING
Getting Started with Python Debugging
VS Code provides powerful Python debugging through the Python extension:
Install Python extension by Microsoft
Create launch.json in .vscode folder
Set breakpoints by clicking in the gutter
Press F5 to start debugging
7 / 64
DEBUG TYPES
Python Debug Configuration Types
1
Python File
Debug a standalone Python script
2
Python Module
Debug a module: python -m module
3
Pytest
Debug pytest tests
4
Django
Debug Django applications
5
Flask
Debug Flask applications
6
Remote Attach
Attach to running Python process
8 / 64
PYTHON FILE
Debug a Python File
{
"name" : "Python: Debug File" ,
"type" : "debugpy" ,
"request" : "launch" ,
"program" : "${workspaceFolder}/main.py" ,
"console" : "integratedTerminal" ,
"justMyCode" : true,
"env" : {
"DEBUG" : "true"
}
}
launch.json Python File
9 / 64
PYTHON MODULE
Debug a Python Module
{
"name" : "Python: Module" ,
"type" : "debugpy" ,
"request" : "launch" ,
"module" : "fastapi" ,
"args" : [
"dev" ,
"--port" , "8080"
],
"cwd" : "${workspaceFolder}/backend" ,
"console" : "integratedTerminal" ,
"justMyCode" : true
}
module fastapi
10 / 64
PYTEST DEBUG
Debug pytest Tests
{
"name" : "Python: Pytest" ,
"type" : "debugpy" ,
"request" : "launch" ,
"module" : "pytest" ,
"args" : [
"tests/" ,
"-v" ,
"-s"
],
"console" : "integratedTerminal" ,
"justMyCode" : false,
"env" : {
"PYTHONPATH" : "${workspaceFolder}"
}
}
pytest Testing
11 / 64
DJANGO DEBUG
Debug Django Application
{
"name" : "Python: Django" ,
"type" : "debugpy" ,
"request" : "launch" ,
"module" : "django" ,
"args" : [
"runserver" ,
"--noreload"
],
"django" : true,
"justMyCode" : true,
"env" : {
"DJANGO_SETTINGS_MODULE" : "mysite.settings"
}
}
Django Web Framework
12 / 64
FRONTEND
Frontend Debugging
JavaScript & Browser Debugging
13 / 64
FRONTEND DEBUG
Frontend Debugging Options
VS Code supports multiple frontend debugging scenarios:
Chrome/Edge debugging with built-in launch config
Firefox debugging with extension
Node.js debugging for backend JavaScript
Electron debugging for desktop apps
Attach to running browser sessions
14 / 64
CHROME LAUNCH
Launch Chrome for Debugging
{
"name" : "Chrome: Launch" ,
"type" : "chrome" ,
"request" : "launch" ,
"url" : "http://localhost:5173" ,
"webRoot" : "${workspaceFolder}/src" ,
"sourceMaps" : true,
"preLaunchTask" : "npm: dev" ,
"runtimeArgs" : [
"--disable-web-security"
]
}
Chrome Launch
15 / 64
CHROME ATTACH
Attach to Running Chrome
{
"name" : "Chrome: Attach" ,
"type" : "chrome" ,
"request" : "attach" ,
"port" : 9222,
"url" : "http://localhost:5173" ,
"webRoot" : "${workspaceFolder}/src" ,
"sourceMaps" : true
}
/* Start Chrome with remote debugging:
chrome --remote-debugging-port=9222 */
Chrome Attach
16 / 64
NODE DEBUG
Debug Node.js Application
{
"name" : "Node: Launch" ,
"type" : "node" ,
"request" : "launch" ,
"program" : "${workspaceFolder}/server/index.js" ,
"console" : "integratedTerminal" ,
"runtimeExecutable" : "node" ,
"skipFiles" : ["<node_internals>/**" ]
}
{
"name" : "Node: Nodemon" ,
"type" : "node" ,
"request" : "launch" ,
"runtimeExecutable" : "nodemon" ,
"program" : "${workspaceFolder}/server/index.js" ,
"console" : "integratedTerminal"
}
Node.js Server
17 / 64
ELECTRON DEBUG
Debug Electron Application
{
"name" : "Electron: Main" ,
"type" : "node" ,
"request" : "launch" ,
"runtimeExecutable" : "${workspaceFolder}/node_modules/.bin/electron" ,
"program" : "${workspaceFolder}/main.js" ,
"console" : "integratedTerminal"
}
{
"name" : "Electron: Renderer" ,
"type" : "chrome" ,
"request" : "launch" ,
"url" : "file://${workspaceFolder}/index.html" ,
"webRoot" : "${workspaceFolder}"
}
Electron Desktop
18 / 64
CONTROLS
Debugging Controls
Mastering the Debugger
19 / 64
KEYBOARD SHORTCUTS
Debug Control Keys
F5
Continue
Run to next breakpoint
F9
Toggle BP
Enable/disable breakpoint
F10
Step Over
Execute current line
F11
Step Into
Enter function call
Shift+F11
Step Out
Exit current function
Shift+F5
Stop
Stop debugging session
20 / 64
BREAKPOINT TYPES
Types of Breakpoints
Standard Breakpoint
Pause at specific line
Click in gutter (left margin)
Red circle icon
Conditional variants available
Special Breakpoints
Exception Breakpoint - on error
Function Breakpoint - named function
Conditional - expression true
Hit Count - after N hits
21 / 64
CONDITIONAL BP
Conditional Breakpoints
/* Right-click on breakpoint → Edit Condition */
/* Condition: only pause when true */
user.is_admin == true
/* Hit Count: pause after N hits
Format: "> 5" (greater than 5)
"== 10" (equals 10)
"% 3 == 0" (every 3rd hit) */
/* Log Message: print without pausing
Format: "Log: user = " + user.name */
Conditional Breakpoints
22 / 64
DEBUG ACTIONS
Debug Toolbar Actions
The debug toolbar provides essential controls:
Continue/Pause (F5) - Run or pause execution
Step Over (F10) - Execute line, skip function details
Step Into (F11) - Enter function to debug
Step Out (Shift+F11) - Exit current function
Restart (Ctrl+Shift+F5) - Restart debug session
Stop (Shift+F5) - End debugging
23 / 64
VARIABLES
Variable Viewer
Inspecting Application State
24 / 64
VARIABLES PANEL
Understanding the Variables Panel
The Variables panel shows current scope state:
Locals - Variables in current function
Globals - Module-level variables
Registers - CPU register values
Expand objects to see properties
Right-click to copy values
25 / 64
WATCH EXPRESSIONS
Using Watch Expressions
Add to Watch
Right-click variable → Add to Watch
Click + in Watch panel
Type any valid expression
Updates in real-time
Watch Examples
user.name - Object property
len(items) - Function call
result['key'] - Dictionary access
type(x).__name__ - Type inspection
26 / 64
CALL STACK
The Call Stack Panel
The call stack shows execution history:
Displays function call chain
Click any frame to inspect state
Right-click to jump to code
Gray frames = external code
Expand to see thread info
27 / 64
DEBUG CONSOLE
Debug Console
Execute code in the debug context:
Access via View → Debug Console
Evaluate expressions in scope
Call functions with test args
Import modules for testing
Temporary debugging code
28 / 64
DEBUG CONSOLE
Debug Console Examples
class ="code-comment">/* In Debug Console: */
class ="code-comment">/* Inspect variables */
user
user.name
user.is_active
class ="code-comment">/* Execute code */
print(f"User: {user.name}")
result = calculate_total(items)
class ="code-comment">/* Test class ="code-keyword">function calls */
response = api.get_user(user_id)
response.json()
class ="code-comment">/* Modify values (careful!) */
count = 100
Debug Console REPL
29 / 64
PART 1
The Modern Python Environment
15 minutes
30 / 64
PART 1
Multi-Directory Project Structure
Real-world projects span multiple directories:
backend/ - FastAPI server (Python)
frontend/ - Vite + Electron (JavaScript)
Shared configurations and dependencies
Each with its own package manager
31 / 64
PROJECT STRUCTURE
Typical Monorepo Layout
# Project root structure
$ ls -la myproject/
drwxr-xr-x myproject/
-rw-r--r-- package.json
-rw-r--r-- pyproject.toml
$ ls myproject/backend/
main.py app/ uv.lock pyproject.toml
$ ls myproject/frontend/
src/ package.json vite.config.ts
32 / 64
UV - MODERN PYTHON
Why uv?
Traditional pip
Slow package resolution
No integrated virtualenv
Separate tool for environments
Large memory footprint
uv (Ultra Fast)
10-100x faster than pip
Built-in venv management
Single binary installation
Rust-powered performance
33 / 64
UV SETUP
Installing and Using uv
$ # Install uv (macOS/Linux)
curl -LsSf https://astral.sh/uv/install.sh | sh
$ # Run Python with uv
uv run python main.py
$ # Run FastAPI with uv (auto-installs deps)
uv run fastapi dev --port 8080
$ # Create virtual environment
uv venv && uv sync
34 / 64
EXERCISE 1
Exercise: Create a Basic Task
Let's create a VS Code task to run FastAPI via uv run:
Open Command Palette → Tasks: Configure Task
Create a new tasks.json for a npm script style command
Configure: uv run fastapi dev --port 8080
Test running it with Tasks: Run Task
35 / 64
TASKS.JSON EXAMPLE
Basic FastAPI Task
{
"version" : "2.0.0" ,
"tasks" : [
{
"label" : "Run FastAPI Server" ,
"type" : "shell" ,
"command" : "uv run fastapi dev --port 8080" ,
"problemMatcher" : [],
"isBackground" : false,
"presentation" : {
"reveal" : "always" ,
"panel" : "new"
}
}
]
}
tasks.json FastAPI
36 / 64
PART 2
Bridging the Gap with Tasks
15 minutes
37 / 64
TASKS.JSON
Anatomy of tasks.json
1
label
Unique identifier for the task
2
type
shell, process, or npm
3
command
The actual command to execute
4
problemMatcher
Parse output for errors
5
isBackground
Keep running for dev servers
38 / 64
BACKGROUND TASKS
Using isBackground: true
{
"label" : "Start: Vite Dev Server" ,
"type" : "shell" ,
"command" : "npm run dev" ,
"isBackground" : true,
"problemMatcher" : [
"$tsc-watch" ,
{
"owner" : "vite" ,
"pattern" : {
"regexp" : "^.*$" ,
"message" : 0
},
"background" : {
"activeOnStart" : true,
"beginsPattern" : ".*VITE.*" ,
"endsPattern" : ".*ready in.*"
}
}
]
}
isBackground Vite
39 / 64
PROBLEM MATCHERS
What Are Problem Matchers?
Problem matchers parse CLI output to:
Detect when services are ready (not just started)
Parse error messages for the Problems panel
Enable intelligent task chaining
Provide visual feedback in VS Code
40 / 64
PROBLEM MATCHER SIGNALS
Common Ready Signals
# FastAPI/Uvicorn
Application startup complete: Uvicorn running on http://0.0.0.0:8000
# Vite
Local: http://localhost:5173/ Network: use --host to expose
# Django
Starting development server at http://127.0.0.1:8000/
# Flask
Running on http://127.0.0.1:5000
41 / 64
REGEX PATTERNS
Crafting Regex for ProblemMatcher
{
"problemMatcher" : {
"owner" : "fastapi" ,
"fileLocation" : "relative" ,
"pattern" : {
"regexp" : "^Application startup complete: (.+)$" ,
"message" : 1
},
"background" : {
"activeOnStart" : true,
"beginsPattern" : "^.*Uvicorn.*$" ,
"endsPattern" : "Application startup complete"
}
}
}
Regex Pattern
42 / 64
EXERCISE 2
Exercise: Background Task with Matcher
Create a background task that:
Runs FastAPI with uv run
Waits for 'Application startup complete' signal
Reports ready status in VS Code
Practice with regex pattern matching
43 / 64
TASK CHAINING
Task Dependencies
Without Dependencies
- Manual start order
- Guess when services are ready
- Race conditions
- Time wasted waiting
With Dependencies
+ Automatic startup sequence
+ Problem matcher ensures ready
+ No race conditions
+ One-click full stack start
44 / 64
PART 3
The 'Remote Attach' Pattern
15 minutes
45 / 64
ATTACH VS LAUNCH
Why Attach > Launch?
For complex managed environments, Attach is superior:
Debug processes you didn't start
Works with uv, docker, docker-compose
No need to modify your run commands
Attach to already-running processes
Debug production-like environments
46 / 64
DEBUGPY SETUP
Starting FastAPI with debugpy
$ # Install debugpy
uv add --dev debugpy
$ # Run with debugpy listening on port 5678
uv run debugpy --listen 5678 --wait-for-client -m fastapi dev --port 8081
# # Alternative: Add to your code
$ import debugpy
debugpy.listen(("0.0.0.0", 5678))
47 / 64
LAUNCH.JSON
Remote Attach Configuration
{
"name" : "Python: Remote Attach" ,
"type" : "debugpy" ,
"request" : "attach" ,
"connect" : {
"host" : "localhost" ,
"port" : 5678
},
"pathMappings" : [
{
"localRoot" : "${workspaceFolder}/backend" ,
"remoteRoot" : "/app"
}
],
"justMyCode" : true
}
debugpy Attach
48 / 64
PATH MAPPINGS
Understanding pathMappings
pathMappings bridges local and remote file systems:
localRoot: Your local code location
remoteRoot: Where code runs (container, VM)
VS Code maps breakpoints correctly
Essential for Docker/remote development
49 / 64
PORT SELECTION
Choosing the Right Port
Common Debug Ports
5678 - debugpy default
9229 - Node.js debug
5858 - Legacy Python
4000-4999 - Custom range
Best Practices
Use consistent port across team
Document in README
Avoid port conflicts
Consider environment variables
50 / 64
EXERCISE 3
Exercise: Remote Attach Setup
Configure remote attach debugging:
Start FastAPI with debugpy on port 5678
Create launch.json with attach config
Set up pathMappings for your project
Attach and set a breakpoint
51 / 64
PART 4
The One-Click Workflow
20 minutes
52 / 64
COMPOUND CONFIGURATIONS
What Are Compounds?
Compounds let you launch multiple debug configurations at once:
Group Python and JavaScript debuggers
Start all services with one click
Unified debugging experience
Single stop terminates all
53 / 64
COMPOUND EXAMPLE
Creating a Compound Config
{
"compounds" : [
{
"name" : "Full Stack Debug" ,
"configurations" : [
"Python: FastAPI Attach" ,
"Chrome: Launch Frontend"
],
"stopAll" : true
}
],
"configurations" : [
{
"name" : "Python: FastAPI Attach" ,
"type" : "debugpy" ,
"request" : "attach" ,
"connect" : { "port" : 5678 },
"preLaunchTask" : "Start FastAPI"
},
{
"name" : "Chrome: Launch Frontend" ,
"type" : "chrome" ,
"request" : "launch" ,
"url" : "http://localhost:5173" ,
"webRoot" : "${workspaceFolder}/frontend"
}
]
}
Compounds Full Stack
54 / 64
PRELAUNCH TASK
The preLaunchTask Property
preLaunchTask runs a task before debugging starts:
Start backend services first
Wait for services to be ready
Chain multiple tasks together
Ensure dependencies are running
55 / 64
FULL WORKFLOW
Complete One-Click Setup
// tasks.json
{
"tasks" : [
{
"label" : "Start: FastAPI (debugpy on :5678)" ,
"type" : "shell" ,
"command" : "uv run debugpy --listen 5678 --wait-for-client -m fastapi dev --port 8081" ,
"isBackground" : true,
"problemMatcher" : {
"owner" : "fastapi-debugpy" ,
"background" : {
"activeOnStart" : true,
"beginsPattern" : ".*FastAPI\\s+Starting development server.*" ,
"endsPattern" : ".*Application startup complete.*"
}
}
},
{
"label" : "Start: Vite Dev Server" ,
"type" : "shell" ,
"command" : "npm run dev" ,
"isBackground" : true,
"problemMatcher" : [
"$tsc-watch" ,
{
"owner" : "vite" ,
"pattern" : {
"regexp" : "^.*$" ,
"message" : 0
},
"background" : {
"activeOnStart" : true,
"beginsPattern" : ".*VITE.*" ,
"endsPattern" : ".*ready in.*"
}
}
]
}
]
}
// launch.json - compound
{
"compounds" : [
{
"name" : "Debug Full Stack" ,
"configurations" : [
"Python: FastAPI Attach" ,
"JavaScript: Chrome"
],
"stopAll" : true
}
]
}
Complete Workflow
56 / 64
STOPALL
Understanding stopAll: true
When you stop debugging:
stopAll: true terminates ALL processes in compound
Clean exit for all services
No orphaned processes
Essential for background tasks
57 / 64
BEFORE VS AFTER
The Transformation
Before
- 3+ terminal tabs open
- Manual start order
- Guess when ready
- Separate debug sessions
- Hard to reproduce issues
After
+ One click starts all
+ Automatic ordering
+ Problem matchers ensure ready
+ Unified debugger
+ Full stack breakpoints
58 / 64
EXERCISE 4
Exercise: Build Your One-Click Workflow
Create a complete debugging workflow:
Configure background tasks for FastAPI + Vite
Set up problem matchers for both
Create attach configs for both
Build a compound with stopAll: true
Test the one-click experience
59 / 64
SUMMARY
What We Covered
1
Basics
Python file, module, pytest, Django configs
2
Frontend
Chrome, Node.js, Electron debugging
3
Controls
F5/F10/F11, breakpoints, debug actions
4
Variables
Watch, call stack, debug console
5
Part 1
Modern Python env with uv
6
Part 2
tasks.json, isBackground, problemMatchers
7
Part 3
Remote Attach with debugpy
8
Part 4
Compounds, preLaunchTask, stopAll
60 / 64
NEXT STEPS
Continue Your Journey
Apply these patterns to your projects
Explore Docker debugging with pathMappings
Try debugging with docker-compose
Share configurations with your team
Add to your project templates
61 / 64
╔══════════════════════════════════════╗
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are by definition not smart enough to debug it."
╚══════════════════════════════════════╝
— Brian Kernighan
62 / 64
Q&A
Questions & Hands-On Practice
Let's apply what we've learned:
Try the one-click debug workflow
Experiment with compound configurations
Add debugpy to your own projects
Ask questions and share experiences
63 / 64
THANK YOU
Multi-Stack Debug Mastery
Questions? | @satyamsoni2211 | PyConf 2026
64 / 64