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.

2 / 64
╔══════════════════════════════════════╗
"The best debugging tool is still careful thought, coupled with well-designed code."
╚══════════════════════════════════════╝

— Brian Kernighan

3 / 64

Workshop Timeline (90 minutes)

5
min
15
min
15
min
15
min
20
min
20
min

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 CodePython
02
Frontend Debug
Debug JavaScript/TypeScript in Chrome and Electron
ChromeDevTools
03
Debug Controls
Master breakpoints, stepping, and debugging actions
ControlsF5/F10/F11
04
Variable Viewer
Inspect locals, globals, and watch expressions
VariablesWatch
05
Orchestrate Tasks
Automate services with tasks.json
TasksAutomation
06
Remote Attach
Debug Python apps on specific ports with debugpy
debugpyAttach
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:

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.jsonPython 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
}
modulefastapi
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}"
  }
}
pytestTesting
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"
  }
}
DjangoWeb Framework
12 / 64
FRONTEND

Frontend Debugging

JavaScript & Browser Debugging

13 / 64
FRONTEND DEBUG

Frontend Debugging Options

VS Code supports multiple frontend debugging scenarios:

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"
  ]
}
ChromeLaunch
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 */
ChromeAttach
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.jsServer
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}"
}
ElectronDesktop
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 */
ConditionalBreakpoints
22 / 64
DEBUG ACTIONS

Debug Toolbar Actions

The debug toolbar provides essential controls:

23 / 64
VARIABLES

Variable Viewer

Inspecting Application State

24 / 64
VARIABLES PANEL

Understanding the Variables Panel

The Variables panel shows current scope state:

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:

27 / 64
DEBUG CONSOLE

Debug Console

Execute code in the debug context:

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 ConsoleREPL
29 / 64
PART 1

The Modern Python Environment

15 minutes

30 / 64
PART 1

Multi-Directory Project Structure

Real-world projects span multiple directories:

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:

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.jsonFastAPI
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.*"
      }
    }
  ]
}
isBackgroundVite
39 / 64
PROBLEM MATCHERS

What Are Problem Matchers?

Problem matchers parse CLI output to:

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"
    }
  }
}
RegexPattern
42 / 64
EXERCISE 2

Exercise: Background Task with Matcher

Create a background task that:

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:

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
}
debugpyAttach
48 / 64
PATH MAPPINGS

Understanding pathMappings

pathMappings bridges local and remote file systems:

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:

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:

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"
    }
  ]
}
CompoundsFull Stack
54 / 64
PRELAUNCH TASK

The preLaunchTask Property

preLaunchTask runs a task before debugging starts:

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
    }
  ]
}
CompleteWorkflow
56 / 64
STOPALL

Understanding stopAll: true

When you stop debugging:

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:

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

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:

63 / 64
THANK YOU

Multi-Stack Debug Mastery

Questions? | @satyamsoni2211 | PyConf 2026

64 / 64