<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Christopher Semler</title>
    <description>The latest articles on DEV Community by Christopher Semler (@sem7ac).</description>
    <link>https://dev.to/sem7ac</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4076919%2Ff23707a2-8e8a-45bb-b593-e2cb9a2b572d.jpg</url>
      <title>DEV Community: Christopher Semler</title>
      <link>https://dev.to/sem7ac</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sem7ac"/>
    <language>en</language>
    <item>
      <title>Visual Studio 2026 Debugger Detection Failure</title>
      <dc:creator>Christopher Semler</dc:creator>
      <pubDate>Fri, 14 Aug 2026 03:21:38 +0000</pubDate>
      <link>https://dev.to/sem7ac/visual-studio-2026-debugger-detection-failure-5fip</link>
      <guid>https://dev.to/sem7ac/visual-studio-2026-debugger-detection-failure-5fip</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/bugsmash"&gt;DEV's Summer Bug Smash: Smash Stories&lt;/a&gt; powered by &lt;a href="https://sentry.io/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;

&lt;p&gt;I was building a &lt;strong&gt;Coding Activity Tracker&lt;/strong&gt; 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 &lt;em&gt;anything&lt;/em&gt;, because breakpoints completely change how an app behaves.&lt;/p&gt;

&lt;p&gt;Running the tracker standalone meant it had to detect &lt;strong&gt;external&lt;/strong&gt; debugging sessions.&lt;br&gt;&lt;br&gt;
&lt;code&gt;Debugger.IsAttached&lt;/code&gt; only detects debugging of the &lt;strong&gt;current process&lt;/strong&gt;, so standalone mode always reported “no debugger,” even when Visual Studio was actively debugging another project.&lt;/p&gt;

&lt;p&gt;That single limitation broke the entire purpose of the tracker.&lt;/p&gt;

&lt;p&gt;The tracker had to detect debugging &lt;strong&gt;even when it wasn’t the app being debugged&lt;/strong&gt;.&lt;/p&gt;


&lt;h1&gt;
  
  
  What Was Tried
&lt;/h1&gt;

&lt;p&gt;Once it became obvious that &lt;code&gt;Debugger.IsAttached&lt;/code&gt; was useless for standalone mode, I started trying every simple, reasonable approach that &lt;em&gt;should&lt;/em&gt; have worked but didn’t.&lt;/p&gt;

&lt;p&gt;Parent‑process tracing&lt;/p&gt;

&lt;p&gt;&lt;code&gt;int parentPid = GetParentProcessId(targetProcess);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;WMI queries&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var query = new ManagementObjectSearcher("SELECT * FROM Win32_Process WHERE ProcessId = " + pid);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Slow, stale, inconsistent, and occasionally wrong. Not usable in real‑time tracking.&lt;/p&gt;

&lt;p&gt;Process‑tree walking&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var children = GetChildProcesses(vsProcess.Id);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Visual Studio’s process tree is chaos. Helper processes spawn and die constantly. None reliably indicate debugging.&lt;/p&gt;

&lt;p&gt;Handle inspection&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var handles = GetProcessHandles(targetProcess);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;There is no stable “debugging handle” pattern. Different projects produce different handle sets.&lt;/p&gt;

&lt;p&gt;Thread‑freeze detection&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bool frozen = targetProcess.Threads.Cast&amp;lt;ProcessThread&amp;gt;()&lt;br&gt;
.Any(t =&amp;gt; t.ThreadState == ThreadState.Wait);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Breakpoints freeze the debugger, not the tracker. And threads freeze for normal reasons too. Tons of false positives.&lt;/p&gt;

&lt;p&gt;CPU sampling&lt;/p&gt;

&lt;p&gt;&lt;code&gt;float cpu = GetCpuUsage(targetProcess);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Breakpoints sometimes drop CPU, sometimes don’t. Idle apps already sit at zero. No reliable pattern.&lt;/p&gt;

&lt;p&gt;Window‑class correlation&lt;/p&gt;

&lt;p&gt;&lt;code&gt;IntPtr hwnd = FindWindow("HwndWrapper", projectName);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Visual Studio creates and destroys windows constantly. No stability.&lt;/p&gt;

&lt;p&gt;Tracking &lt;code&gt;devenv.exe&lt;/code&gt; child lifecycles&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var children = GetChildProcesses(vsProcess.Id);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;VS spawns random helper processes for IntelliSense, diagnostics, test runners, service hubs, etc. None of them reliably indicate debugging.&lt;/p&gt;
&lt;h1&gt;
  
  
  The solution
&lt;/h1&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;DetectDebugger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Ignore debugging of CodeActivityTracker itself&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Debugger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsAttached&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Find Visual Studio&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;vs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProcessesByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"devenv"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vs&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Get VS window title&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MainWindowTitle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Example title:&lt;/span&gt;
    &lt;span class="c1"&gt;// "VoidPulse (Running) - dev_notes.txt - Microsoft Visual Studio"&lt;/span&gt;
    &lt;span class="c1"&gt;// We want: "VoidPulse"&lt;/span&gt;

    &lt;span class="c1"&gt;// Split at " - "&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;" - "&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;StringSplitOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;projectPart&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// "VoidPulse (Running)"&lt;/span&gt;

    &lt;span class="c1"&gt;// Remove " (Running)" or " (Debugging)" or similar suffixes&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;projectPart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;" ("&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;projectPart&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;projectPart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;projectName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;projectPart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Now check if the debug target process exists&lt;/span&gt;
    &lt;span class="c1"&gt;// If the project is "VoidPulse", the process is "VoidPulse.exe"&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;debugTargets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProcessesByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;debugTargets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>bugsmash</category>
    </item>
  </channel>
</rss>
