This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry.
Background
I was building a Coding Activity Tracker to give me realistic timing for how long I actually spend coding â typing, reading, debugging, idle, everything. For that to work, it needed to know when Visual Studio was debugging anything, because breakpoints completely change how an app behaves.
Running the tracker standalone meant it had to detect external debugging sessions.
Debugger.IsAttached only detects debugging of the current process, so standalone mode always reported âno debugger,â even when Visual Studio was actively debugging another project.
That single limitation broke the entire purpose of the tracker.
The tracker had to detect debugging even when it wasnât the app being debugged.
What Was Tried
Once it became obvious that Debugger.IsAttached was useless for standalone mode, I started trying every simple, reasonable approach that should have worked but didnât.
Parentâprocess tracing
int parentPid = GetParentProcessId(targetProcess);
Fails because Visual Studio doesnât always launch the debug target. Sometimes the user launches it manually. Sometimes VS attaches to an alreadyârunning process.
WMI queries
var query = new ManagementObjectSearcher("SELECT * FROM Win32_Process WHERE ProcessId = " + pid);
Slow, stale, inconsistent, and occasionally wrong. Not usable in realâtime tracking.
Processâtree walking
var children = GetChildProcesses(vsProcess.Id);
Visual Studioâs process tree is chaos. Helper processes spawn and die constantly. None reliably indicate debugging.
Handle inspection
var handles = GetProcessHandles(targetProcess);
There is no stable âdebugging handleâ pattern. Different projects produce different handle sets.
Threadâfreeze detection
bool frozen = targetProcess.Threads.Cast<ProcessThread>()
.Any(t => t.ThreadState == ThreadState.Wait);
Breakpoints freeze the debugger, not the tracker. And threads freeze for normal reasons too. Tons of false positives.
CPU sampling
float cpu = GetCpuUsage(targetProcess);
Breakpoints sometimes drop CPU, sometimes donât. Idle apps already sit at zero. No reliable pattern.
Windowâclass correlation
IntPtr hwnd = FindWindow("HwndWrapper", projectName);
Visual Studio creates and destroys windows constantly. No stability.
Tracking devenv.exe child lifecycles
var children = GetChildProcesses(vsProcess.Id);
VS spawns random helper processes for IntelliSense, diagnostics, test runners, service hubs, etc. None of them reliably indicate debugging.
The solution
The solution ended up being stupidly simple compared to all the garbage we tried. Visual Studio always puts the active project name in the window title when itâs debugging. Thatâs the one reliable external signal we actually get. So the tracker reads the Visual Studio window title, extracts the project name, and then checks if a process with that name is running. If both are true, Visual Studio is debugging.
private bool DetectDebugger()
{
// Ignore debugging of CodeActivityTracker itself
if (Debugger.IsAttached)
return false;
// Find Visual Studio
var vs = Process.GetProcessesByName("devenv").FirstOrDefault();
if (vs == null)
return false;
// Get VS window title
string title;
try
{
title = vs.MainWindowTitle;
}
catch
{
return false;
}
if (string.IsNullOrWhiteSpace(title))
return false;
// Example title:
// "VoidPulse (Running) - dev_notes.txt - Microsoft Visual Studio"
// We want: "VoidPulse"
// Split at " - "
var parts = title.Split(new[] { " - " }, StringSplitOptions.None);
if (parts.Length == 0)
return false;
string projectPart = parts[0]; // "VoidPulse (Running)"
// Remove " (Running)" or " (Debugging)" or similar suffixes
int idx = projectPart.IndexOf(" (");
if (idx > 0)
projectPart = projectPart.Substring(0, idx);
string projectName = projectPart.Trim();
if (string.IsNullOrWhiteSpace(projectName))
return false;
// Now check if the debug target process exists
// If the project is "VoidPulse", the process is "VoidPulse.exe"
try
{
var debugTargets = Process.GetProcessesByName(projectName);
return debugTargets.Any();
}
catch
{
return false;
}
}
Summary
After trying every detection trick we could think of â parentâprocess tracing, WMI queries, processâtree walking, handle inspection, threadâfreeze detection, CPU sampling, windowâclass correlation, and even tracking devenv child lifecycles â all of them failed for different reasons. They were slow, inconsistent, unreliable, or flatâout wrong depending on how Visual Studio decided to behave that day.
The only method that consistently worked was the simplest one: read the Visual Studio window title, extract the active project name, and check if the matching process is running. If both line up, Visual Studio is debugging. Itâs not fancy, itâs not magical, but itâs stable, predictable, and it actually works in the real world.
Thatâs the solution the tracker ships with, because itâs the only one that didnât fall apart the moment Visual Studio did something weird.
Top comments (0)