<?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: Mohi Uddin</title>
    <description>The latest articles on DEV Community by Mohi Uddin (@mohi_uddin).</description>
    <link>https://dev.to/mohi_uddin</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%2F4001061%2Fcfd1c5b7-75c3-412e-9c2b-c285c2be7d95.png</url>
      <title>DEV Community: Mohi Uddin</title>
      <link>https://dev.to/mohi_uddin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mohi_uddin"/>
    <language>en</language>
    <item>
      <title>The Hardest Bug to Find Was a False Positive.</title>
      <dc:creator>Mohi Uddin</dc:creator>
      <pubDate>Sun, 02 Aug 2026 01:00:00 +0000</pubDate>
      <link>https://dev.to/mohi_uddin/the-hardest-bug-to-find-was-a-false-positive-48cc</link>
      <guid>https://dev.to/mohi_uddin/the-hardest-bug-to-find-was-a-false-positive-48cc</guid>
      <description>&lt;h1&gt;
  
  
  The Benchmark Behind Proactive Scan
&lt;/h1&gt;

&lt;p&gt;On Tuesday we shipped Proactive Scan: one command that ranks your riskiest files and reads each with cross-file context, before anything crashes.&lt;/p&gt;

&lt;p&gt;Today we're publishing the benchmark behind it. All of it: the numbers, the methodology, the part where our first version cried wolf, and the kinds of bugs it still misses.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;28 test cases, written fresh for this benchmark. 20 files with planted defects, 8 clean files as controls.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;th&gt;What was planted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single-file defects&lt;/td&gt;
&lt;td&gt;8/8&lt;/td&gt;
&lt;td&gt;SQL injection, resource leak, division by zero, mutable default arg, bare except, missing await, off-by-one, hardcoded secret&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-file defects&lt;/td&gt;
&lt;td&gt;12/12&lt;/td&gt;
&lt;td&gt;wrong argument count, missing export, return-shape mismatch, type mismatch, zero-value config, interface drift, async race, renamed function, generic type misuse, optional and null flow, config modulo, callback arity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False alarms on clean files&lt;/td&gt;
&lt;td&gt;0/8&lt;/td&gt;
&lt;td&gt;error handling, executemany, type narrowing, async patterns, optional chaining, class design&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run on July 2, 2026 against our staging API, which runs the same pipeline as production: Voyage embeddings for the codebase index, Claude Haiku 4.5 for analysis.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This is our corpus, scored by us, with n=28.&lt;/strong&gt; It says the scan does what we designed it to do. It does not say we beat anyone. We ran no competitors, so you will find no competitor numbers here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why We Wrote the Cases From Scratch
&lt;/h2&gt;

&lt;p&gt;The lazy way to build this benchmark is to grab known bugs from popular open-source repos. We had exactly that lying around from an internal regression test. We didn't publish it, for one reason: models have read those repos. A detector that recognizes a famous React bug from training data is doing recall, not analysis.&lt;/p&gt;

&lt;p&gt;So every one of the 28 cases was written fresh in late June. Original code, original planted defects, patterned after real-world failure classes but not copied from anything public. No model has seen them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Cross-File Case Looks Like
&lt;/h2&gt;

&lt;p&gt;The single-file cases are table stakes. Any decent linter flags a bare &lt;code&gt;except&lt;/code&gt;. The 12 cross-file cases are the reason the scan exists, because they're invisible unless the tool knows the rest of your project.&lt;/p&gt;

&lt;p&gt;Here is XF-05, two files, trimmed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# limits.py
# requests per window; 0 disables (but callers divide by it)
&lt;/span&gt;&lt;span class="n"&gt;WINDOW_SLOTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;# rate.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;limits&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WINDOW_SLOTS&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;per_slot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&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;total&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;WINDOW_SLOTS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;rate.py&lt;/code&gt; is flawless on its own. Every line is correct. The bug lives in the relationship: a config value in another file is zero, and this file divides by it. Point a single-file tool at &lt;code&gt;rate.py&lt;/code&gt; and it shrugs. The scan indexes both files, pulls &lt;code&gt;limits.py&lt;/code&gt; in as context when it reads &lt;code&gt;rate.py&lt;/code&gt;, and reports the &lt;code&gt;ZeroDivisionError&lt;/code&gt; waiting to happen.&lt;/p&gt;

&lt;p&gt;That's the pattern for all 12 cross-file cases: the buggy file is locally clean and the defect only exists given a fact from elsewhere in the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Scoring Works
&lt;/h2&gt;

&lt;p&gt;Each case declares an expected signal: keywords and an issue class the finding must mention (for XF-05: "division by zero" or the offending constant's name). A case counts as detected when the scan reports a finding on the planted file matching that signal. A clean file passes when the scan returns nothing.&lt;/p&gt;

&lt;p&gt;Lenient keyword matching means we're grading "did it see the problem," not "did it phrase the problem the way we would." We wrote both the cases and the grader, which is a real conflict, and the mitigation is transparency: the corpus, the expected signals, and the runner are published, so you can re-run it and disagree with our scoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  The False Positive War
&lt;/h2&gt;

&lt;p&gt;The headline number everyone asks about is detection. The number that decides whether anyone keeps the tool installed is false alarms. A scan that flags healthy code trains you to ignore it, and then it's worse than no scan.&lt;/p&gt;

&lt;p&gt;Our git history tells the story honestly. Three shipped prompt versions in five days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;scan-v3.&lt;/strong&gt; First version with the benchmark in place. Found everything, and also "found" style preferences in clean files. A clean error-handling file got flagged for not re-raising. Wolf, cried.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;scan-v4.&lt;/strong&gt; Carved out an entire false-positive class: code that handles external or unknown input defensively was being flagged for the input being unknown. The fix was prompt discipline: a finding must name a concrete failure, with concrete inputs, reachable from the code as written. "This could be risky" is not a finding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;scan-v5.&lt;/strong&gt; One control file (CLEAN-06, async patterns) still produced an occasional hedge-flag. Hardened the instruction: if the code is correct, say nothing. Zero findings is a valid, good answer.&lt;/p&gt;

&lt;p&gt;That last sentence took the longest to make the model believe. Language models want to be helpful, and "I found nothing" doesn't feel helpful. Getting a model to stay quiet on 8 clean files was harder than getting it to talk about 20 buggy ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Misses
&lt;/h2&gt;

&lt;p&gt;The benchmark tests what we planted. It says nothing about several bug classes we know are out there, and honesty about the boundary is the point of publishing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bugs that need runtime data.&lt;/strong&gt; A race that only appears under load, a leak that needs a traffic pattern. Static reading won't catch these; that's what the debugger side of DebugAI is for, after they fire.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spec bugs.&lt;/strong&gt; Code that does exactly what it says, where what it says is not what the business needed. No tool without your intent can catch these.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scale.&lt;/strong&gt; Fixture files are small. Production files with 2,000 lines and 40 imports are a harder retrieval problem. We test that separately and it is not in this number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Languages.&lt;/strong&gt; The corpus is Python and JavaScript/TypeScript, which is what DebugAI supports. Nothing here generalizes beyond that.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;28 cases is a benchmark, not a proof. A perfect score on 28 cases means the mechanism works, not that the mechanism is perfect.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Reproduce It
&lt;/h2&gt;

&lt;p&gt;The corpus (all 28 cases with expected signals), the runner, and the raw results JSON are public: &lt;code&gt;github.com/1shizaan/debugai-scan-benchmark&lt;/code&gt;. Point the runner at your own API key and re-run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 run.py &lt;span class="nt"&gt;--api-key&lt;/span&gt; dbg_your_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you find a case where our scoring flatters us, open an issue. We'll take the hit in public.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Q: Why not compare against Copilot or Semgrep?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; Because a fair head-to-head is its own project: same corpus, each tool run the way a real user runs it, documented versions, blind scoring, published losses. Rushing that for a launch week would produce exactly the kind of number we don't trust from other vendors. If we do it, it gets its own post.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: Doesn't writing your own test cases guarantee a good score?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; It's a real risk, and the honest answer is partially. We designed cases the scan should catch. The controls are the check on it: a scanner tuned to flag everything would ace detection and fail all 8 clean files. Zero false positives is the half of the score we couldn't game without publishing the evidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: Which model runs the scan?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; Claude Haiku 4.5 with our scan-v5 prompt, over a Voyage-embedded index of the project. Same pipeline on the free tier.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://debugai.io/blog/eval-harness-lessons" rel="noopener noreferrer"&gt;debugai.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>testing</category>
      <category>python</category>
    </item>
    <item>
      <title>Three Silent Bugs That Broke Our AI Evaluation Harness</title>
      <dc:creator>Mohi Uddin</dc:creator>
      <pubDate>Sat, 01 Aug 2026 01:00:00 +0000</pubDate>
      <link>https://dev.to/mohi_uddin/three-silent-bugs-that-broke-our-ai-evaluation-harness-12c9</link>
      <guid>https://dev.to/mohi_uddin/three-silent-bugs-that-broke-our-ai-evaluation-harness-12c9</guid>
      <description>&lt;p&gt;Last week's eval on a truncated JSON bug showed us something uncomfortable: DebugAI can diagnose a bug correctly and still hand you a fix that fails 100% of the time, at 92% claimed confidence.&lt;/p&gt;

&lt;p&gt;One data point isn't a pattern. So we built a harness to run that check automatically, across a real corpus, every time we touch the engine.&lt;/p&gt;

&lt;p&gt;Before the harness produced one number we could trust, it produced three bugs in itself. Here they are, because the failure modes are generic enough that anyone building test tooling will hit them too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;The harness spawns our own MCP server (&lt;code&gt;npx @debugai/mcp&lt;/code&gt;), sends real bugs through the &lt;code&gt;debug_error&lt;/code&gt; tool, and grades the response two ways: does the root cause mention what actually broke, and does the returned fix, applied to a clean copy of the source and run through a real test, actually pass. Confidence gets bucketed against pass rate. No eyeballing.&lt;/p&gt;

&lt;p&gt;Corpus is 11 bugs. Eight are fixtures across common failure classes. Three are real DebugAI production incidents, ported straight from our own commit history, not reconstructed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Key That Wasn't There
&lt;/h2&gt;

&lt;p&gt;The grader's output gets written to a JSONL file, one JSON object per line, so results survive after the terminal scrolls away. First real run, we went to read &lt;code&gt;fixes[i].reason&lt;/code&gt; on a failed fix and got a &lt;code&gt;KeyError&lt;/code&gt;. The key wasn't there.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;JSON.stringify&lt;/code&gt; drops any object key whose value is &lt;code&gt;undefined&lt;/code&gt;. Our grading function set &lt;code&gt;reason&lt;/code&gt; on one code path (a fix that couldn't be applied at all) and never set it on the other (a fix that applied fine but failed the test). Both paths looked complete in the source. Only one of them survived serialization.&lt;/p&gt;

&lt;p&gt;Fix: every return path now sets every key explicitly, &lt;code&gt;null&lt;/code&gt; instead of leaving it unset. &lt;code&gt;JSON.stringify&lt;/code&gt; keeps &lt;code&gt;null&lt;/code&gt;. It only drops &lt;code&gt;undefined&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Patcher That Only Spoke One Language
&lt;/h2&gt;

&lt;p&gt;Half the corpus is Python, ported from our own Python engine code. The other half is JavaScript. The harness applies a fix two ways depending on what the model returns: replace an exact line range, or, when no line number is given, find the named function and replace the whole thing.&lt;/p&gt;

&lt;p&gt;The function-finder only recognized JavaScript syntax: &lt;code&gt;function foo&lt;/code&gt;, &lt;code&gt;const foo = (...) =&amp;gt;&lt;/code&gt;. Nothing for Python's &lt;code&gt;def&lt;/code&gt; or &lt;code&gt;async def&lt;/code&gt;. Every Python fix that arrived without a line number was silently unrunnable, from the first run. It didn't error. It just reported "could not apply," which reads exactly like an ordinary miss.&lt;/p&gt;

&lt;p&gt;Worse: even after teaching it to find &lt;code&gt;def foo&lt;/code&gt;, the code that figures out where the function ends counted curly braces. Python doesn't have any. We had to swap in indentation-based detection: a function ends at the first line that dedents back to or past where it started.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This is the dangerous kind of bug.&lt;/strong&gt; It doesn't crash. It doesn't log anything wrong. It just quietly reports a plausible-looking result that happens to be worse than useless, because it looks identical to a real failure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Today Ate Yesterday
&lt;/h2&gt;

&lt;p&gt;Results write to a folder named by date. Run the harness twice in one day, chasing down a specific bug, and the second run overwrites the first. The "compare against the last run" feature then reports "no previous run found," on a day that very obviously had one.&lt;/p&gt;

&lt;p&gt;Fix: folder names now include the time, not just the date. Old date-only folders still sort correctly as older, so nothing needed migrating.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Actually Taught Us
&lt;/h2&gt;

&lt;p&gt;None of these three bugs are exotic. An &lt;code&gt;undefined&lt;/code&gt; value vanishing from JSON. A regex written for one language and never revisited for the second. A cache key that didn't account for reruns. Individually, forgettable.&lt;/p&gt;

&lt;p&gt;Together, they would have let us publish a confidence-calibration finding while half our own corpus was silently ungradable. The number would have looked exactly as trustworthy as a correct one.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If your eval tooling can fail silently, it will, exactly when you're not looking.&lt;/strong&gt; Build in a self-test that replays one case with a known, already-documented answer, and check the harness reproduces that answer before trusting it on anything new. Ours does. It's how we caught bug two in the first place.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We're not publishing the calibration numbers yet. Three runs so far were mostly cache hits, replaying the same underlying model calls rather than fresh trials, and that's not a strong enough basis for a public claim. Next step is a clean run against a fresh cache window. When that lands, it gets its own post, numbers and all.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Q: Why publish a post about bugs in your own test tooling instead of the actual eval results?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; Because the tooling bugs are the more useful lesson, and the eval results aren't ready to stand behind publicly yet. We'd rather ship the honest version of this post now than sit on it until the bigger number is ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: Is the eval harness open source?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; Not yet. It's a standalone repo, separate from the main product, built specifically to grade &lt;code&gt;debug_error&lt;/code&gt; against a known corpus. If there's interest, we'll consider publishing it.&lt;/p&gt;




&lt;p&gt;DebugAI reads your codebase, not just your stack trace, and hands you a fix you can apply in one click. We grade our own fixes the same way we're asking you to trust them: by running them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://debugai.io/blog/eval-harness-lessons" rel="noopener noreferrer"&gt;debugai.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>javascript</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Higher-Ranked AI Fix Failed. The Lower-Ranked One Passed.</title>
      <dc:creator>Mohi Uddin</dc:creator>
      <pubDate>Fri, 31 Jul 2026 14:55:00 +0000</pubDate>
      <link>https://dev.to/mohi_uddin/the-higher-ranked-ai-fix-failed-the-lower-ranked-one-passed-2oo4</link>
      <guid>https://dev.to/mohi_uddin/the-higher-ranked-ai-fix-failed-the-lower-ranked-one-passed-2oo4</guid>
      <description>&lt;h1&gt;
  
  
  Confidence Numbers Weren't Tracking Correctness. Here's What We Built Instead.
&lt;/h1&gt;

&lt;p&gt;Two fixes came back from the same call last week, ranked by confidence like they always are. The higher-ranked one failed the moment we ran it against the real bug. The lower-ranked one passed.&lt;/p&gt;

&lt;p&gt;We'd been trusting that ranking, the same way anyone reading a DebugAI response trusts it: a bigger number means the model is more sure. So we stopped trusting it and started checking.&lt;/p&gt;

&lt;p&gt;Two weeks ago we found &lt;a href="https://debugai.io/blog/eval-harness-lessons" rel="noopener noreferrer"&gt;three bugs in our own eval harness&lt;/a&gt; before we'd let it grade a single real result. This is what the harness found once we trusted it, and what we built because we couldn't unsee it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Numbers Said
&lt;/h2&gt;

&lt;p&gt;We bucket every fix by its reported confidence, then run it against the real bug and record whether it actually passed. Here's the split from the run we're standing behind:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reported confidence&lt;/th&gt;
&lt;th&gt;Fixes that passed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;80% to 90%&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90% and above&lt;/td&gt;
&lt;td&gt;38%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read that twice. Fixes the model was most sure about passed less often than a coin flip. Fixes in the 80 to 90 percent band, still a band most people would read as "pretty confident," passed exactly none of the time in this run. The number was going up while the odds of it being right were not.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"Passed" here means the fix, applied to a clean copy of the source, made the actual test pass.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not "looked reasonable." Not "the model sounded sure." Ran, on real code, and worked.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Same Shape, Three Times
&lt;/h2&gt;

&lt;p&gt;The first sign of this wasn't the table above. It was a single bug, checked by hand, where two ranked fixes came back at 92% and 85% confidence and both of them failed. That one case could have been noise, so we went looking for more of it, on two separate runs of the harness we'd built specifically to check.&lt;/p&gt;

&lt;p&gt;All three looks found the same shape: confidence going up did not mean correctness going up. Then we rebuilt the harness from scratch in a new home, ran it a fourth time on a fresh 11-bug corpus, and got the table above. Same shape again, on code the earlier runs had never touched.&lt;/p&gt;

&lt;p&gt;Four separate looks, at different times, on different bugs, with different versions of the checking tool. If this were a one-off measurement error, at least one of those runs should have come back clean. None did.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Built Because of It
&lt;/h2&gt;

&lt;p&gt;A confidence number that doesn't track correctness isn't just unhelpful, it's actively misleading, because it looks exactly like a helpful one. So instead of asking the model to grade its own homework, we built a grader that isn't the model.&lt;/p&gt;

&lt;p&gt;For two classes of bug where the check is cheap and deterministic, parse errors and import errors, DebugAI now runs the proposed fix through a real mechanical check before it ever reports a confidence number.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parse Check
&lt;/h3&gt;

&lt;p&gt;Does the fixed code actually parse.&lt;/p&gt;

&lt;p&gt;Not "does it look like valid syntax to a language model," but a real parser, sandboxed, with a hard CPU limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Import Check
&lt;/h3&gt;

&lt;p&gt;Does the import the fix relies on match a file DebugAI actually retrieved for this request.&lt;/p&gt;

&lt;p&gt;Worth being precise about what this claims: it means the import resolves against the context we pulled, &lt;strong&gt;not&lt;/strong&gt; that the package is installed or the function exists at runtime. We say exactly that, nowhere more.&lt;/p&gt;

&lt;p&gt;If the check fails, confidence gets capped at &lt;strong&gt;15&lt;/strong&gt;, no matter what the model originally reported.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;min(fix.confidence, 15)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;when &lt;code&gt;verified&lt;/code&gt; comes back &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The model doesn't get a vote on its own grade once we've actually run the check.&lt;/p&gt;

&lt;p&gt;Both checks run sandboxed, with a five-second budget shared across all candidate fixes for a request. If a check can't finish in time, it doesn't guess. It reports that it didn't finish.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A check that didn't finish and a check that failed are different claims, and we don't let them collapse into each other.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;verified: null&lt;/code&gt; means "we didn't check," either because this bug class isn't covered yet or because the five-second budget ran out.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;verified: false&lt;/code&gt; means "we checked, and it failed."&lt;/p&gt;

&lt;p&gt;Rounding &lt;code&gt;null&lt;/code&gt; up to &lt;code&gt;false&lt;/code&gt;, or down to a silent pass, would make the label lie in exactly the way the original confidence number did.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Seeing It Happen for Real
&lt;/h2&gt;

&lt;p&gt;This isn't theoretical. The first time the check fired against a real request, this is what came back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rank"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verification_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"import(s) not found in retrieved context: billing.pricing (checked against files DebugAI retrieved, not installed packages)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model didn't know the exact name of the function it needed to import. Rather than confidently inventing one, it generated a placeholder and flagged that it needed more context.&lt;/p&gt;

&lt;p&gt;The old behavior would have reported whatever confidence the model felt like reporting on a guess.&lt;/p&gt;

&lt;p&gt;Instead, the check ran, found the import didn't resolve against anything we'd actually retrieved, and the confidence landed exactly where &lt;code&gt;min(fix.confidence, 15)&lt;/code&gt; puts it: &lt;strong&gt;15&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not a dramatic catch, the model was already being honest here, but it's the first real proof the wiring works end to end, on a live request, not a fixture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Doesn't Fix Yet
&lt;/h2&gt;

&lt;p&gt;Parse and import checks cover exactly two bug classes.&lt;/p&gt;

&lt;p&gt;The bucket table above (0% at 80 to 90% confidence, 38% at 90%+) was measured on a corpus that's mostly outside those two classes, and outside those classes, nothing has changed.&lt;/p&gt;

&lt;p&gt;The inversion is still there for &lt;code&gt;TypeError&lt;/code&gt;, &lt;code&gt;RuntimeError&lt;/code&gt;, and everything else that needs a live environment or real data to actually verify, not just parse.&lt;/p&gt;

&lt;p&gt;Type-class checking, the kind that would catch more of what's left, is built behind the same interface and not shipped. It's gated on whether it can run inside the five-second budget without becoming the slow part of every response.&lt;/p&gt;

&lt;p&gt;We're not claiming this closes the gap.&lt;/p&gt;

&lt;p&gt;We're claiming it closes it for the two classes cheap enough to check honestly today, and that the rest stays visibly unverified rather than quietly assumed fine.&lt;/p&gt;

&lt;p&gt;Every fix DebugAI returns now carries one of three states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;null&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;true&lt;/code&gt;&lt;/strong&gt; means we ran a real check and it held.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;false&lt;/code&gt;&lt;/strong&gt; means we ran it and it failed, and the confidence number reflects that whether the model likes it or not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;null&lt;/code&gt;&lt;/strong&gt; means we didn't check, and we say so instead of letting a high number stand in for an answer we don't have.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Q: Does this mean DebugAI's fixes are usually wrong?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; No.&lt;/p&gt;

&lt;p&gt;It means the confidence number attached to a fix wasn't a reliable signal of whether that specific fix would work, for the classes we measured.&lt;/p&gt;

&lt;p&gt;The fixes themselves aren't graded here; the correlation between "how sure the model sounded" and "was it right" is.&lt;/p&gt;

&lt;p&gt;Those are different questions, and conflating them is exactly the mistake this whole feature exists to stop making.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: Will this cover more bug classes over time?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt; Parse and import were the two cheap enough to check mechanically without adding real latency.&lt;/p&gt;

&lt;p&gt;Type checking is built and waiting on a latency decision.&lt;/p&gt;

&lt;p&gt;Anything that needs live execution against real state, most runtime errors, needs a different approach than a five-second sandboxed check, and we're not going to claim we've solved that until we actually have.&lt;/p&gt;




&lt;p&gt;DebugAI reads your codebase, not just your stack trace, and hands you a fix you can apply in one click.&lt;/p&gt;

&lt;p&gt;Now some of those fixes come with a number we checked ourselves, and the ones we haven't checked yet say so.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published at&lt;/em&gt; &lt;strong&gt;debugai.io&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>programming</category>
      <category>testing</category>
    </item>
    <item>
      <title>Your AI gave that fix 92% confidence. Nothing checked it.</title>
      <dc:creator>Mohi Uddin</dc:creator>
      <pubDate>Thu, 30 Jul 2026 19:38:00 +0000</pubDate>
      <link>https://dev.to/mohi_uddin/your-ai-gave-that-fix-92-confidence-nothing-checked-it-564</link>
      <guid>https://dev.to/mohi_uddin/your-ai-gave-that-fix-92-confidence-nothing-checked-it-564</guid>
      <description>&lt;p&gt;Your agent hands you a fix with 92% confidence.&lt;/p&gt;

&lt;p&gt;Ask yourself what produced the 92. Not what it means. What produced it.&lt;/p&gt;

&lt;p&gt;A model wrote that number about its own output. Nothing ran. Nothing parsed. No test executed. It is a language model's estimate of how a language model feels about a language model's suggestion. And it renders in the same font, the same shade of green, as a number that came out of a compiler.&lt;/p&gt;

&lt;p&gt;That is the part I could not stop thinking about while building a debugging tool. Not that AI fixes are wrong. Most are fine. The problem is that the good ones and the bad ones arrive looking identical, so you have to read every one carefully, which is most of the time you were trying to save.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://survey.stackoverflow.co/2025/ai" rel="noopener noreferrer"&gt;Stack Overflow's 2025 Developer Survey&lt;/a&gt; found 84% of developers using AI tools, 45% saying debugging AI-written code takes longer than expected, and 66% naming the top frustration as answers that are "almost right, but not quite".&lt;/p&gt;

&lt;p&gt;Almost right is the expensive failure. Obviously wrong costs five seconds. Almost right costs twenty minutes, three files away, after you have already built on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A field instead of a feeling
&lt;/h2&gt;

&lt;p&gt;So DebugAI returns a &lt;code&gt;verified&lt;/code&gt; field on every proposed fix, and it has three states, not two.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rank"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Add the missing import"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verification_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ast.parse succeeded"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;true&lt;/code&gt;&lt;/strong&gt; means a mechanical check ran and passed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;false&lt;/code&gt;&lt;/strong&gt; means a check ran and failed. The fix still comes back, with its confidence capped, because a failed check is information you want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;null&lt;/code&gt;&lt;/strong&gt; means nothing checked it. The confidence is the model's own estimate and should be read as exactly that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule I hold is that &lt;code&gt;null&lt;/code&gt; never renders as &lt;code&gt;false&lt;/code&gt;, and never renders as nothing at all. "Not checked" and "checked and fine" are different claims. Collapsing them is how a tool starts lying without anyone deciding to lie.&lt;/p&gt;

&lt;p&gt;Here is the part where I lose some of you.&lt;/p&gt;

&lt;h2&gt;
  
  
  We check two kinds of bug. Out of all of them.
&lt;/h2&gt;

&lt;p&gt;Version one of this covers exactly two classes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parse.&lt;/strong&gt; &lt;code&gt;SyntaxError&lt;/code&gt; and &lt;code&gt;IndentationError&lt;/code&gt;. Python goes through &lt;code&gt;ast.parse&lt;/code&gt; in-process. JavaScript goes to &lt;code&gt;node --check&lt;/code&gt; in a temp directory, under a subprocess with a 2 second CPU limit and a 2 second wall clock. This proves the fix's syntax is valid. It proves nothing about whether the fix is correct.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Import.&lt;/strong&gt; &lt;code&gt;ImportError&lt;/code&gt; and &lt;code&gt;ModuleNotFoundError&lt;/code&gt;. This one resolves the named import against the files DebugAI already retrieved for the request.&lt;/p&gt;

&lt;p&gt;And now the sentence that belongs in every tool like this and appears in almost none of them. &lt;strong&gt;The import check does not prove the package is installed.&lt;/strong&gt; The engine has no access to your real dependency graph. A &lt;code&gt;verified: true&lt;/code&gt; on an import check means "this import matches the context we retrieved", not "this import will work when you run it".&lt;/p&gt;

&lt;p&gt;That limitation is written in the source file, above the function, so nobody maintaining it can pretend otherwise later.&lt;/p&gt;

&lt;p&gt;Everything outside those two classes returns &lt;code&gt;null&lt;/code&gt;. Your &lt;code&gt;TypeError&lt;/code&gt; on line 42 gets a diagnosis, ranked fixes, exact edits, and a &lt;code&gt;null&lt;/code&gt; verification, because nothing mechanical checked it. Type-level checking is planned. It is not built, so it does not claim to be.&lt;/p&gt;

&lt;p&gt;Total budget for all of this is 5 seconds across every candidate fix in one request. A check that does not finish returns &lt;code&gt;null&lt;/code&gt;, not &lt;code&gt;false&lt;/code&gt;. Failing to finish is not a verdict.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ship something this narrow
&lt;/h2&gt;

&lt;p&gt;Because the alternative was shipping something broad that guesses, and a guess wearing a checkmark is worse than no checkmark.&lt;/p&gt;

&lt;p&gt;A narrow honest signal is usable. You learn quickly that syntax and import fixes come back green, and that a hard concurrency bug comes back grey, and you calibrate. A broad dishonest signal teaches you nothing except to ignore the badge, which is where most confidence scores have already landed.&lt;/p&gt;

&lt;p&gt;There is a second reason, less noble. Narrow is testable. Two classes with deterministic checkers can be verified in CI. "Our AI validates your fix" cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sandbox, and two things that did not work
&lt;/h2&gt;

&lt;p&gt;Running &lt;code&gt;node --check&lt;/code&gt; on model-written code means running a subprocess on input you did not write. That gets bounded.&lt;/p&gt;

&lt;p&gt;What bounds it today: &lt;code&gt;RLIMIT_CPU&lt;/code&gt; at 2 seconds, plus a wall-clock timeout on the subprocess call, plus a temp directory it cannot see out of.&lt;/p&gt;

&lt;p&gt;What we tried and reverted, on 2026-07-09, empirically rather than by assumption:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;RLIMIT_AS&lt;/code&gt;&lt;/strong&gt; (address space) made &lt;code&gt;node --check&lt;/code&gt; hang or abort with a &lt;code&gt;uv_thread_create&lt;/code&gt; assertion failure on a trivial file, at every value from 128MB to 2048MB. V8 reserves a large virtual address space at startup regardless of actual heap use, so a hard AS cap fights Node's own initialization rather than the candidate code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;RLIMIT_NPROC&lt;/code&gt;&lt;/strong&gt; is a per-real-UID limit on Linux, not a per-subprocess one. Setting it there capped process and thread creation for the entire user account the engine runs as. At 16 it broke Node's worker thread startup outright, with the same abort. At any value it is the wrong instrument: it throttles the whole process, not the sandboxed child.&lt;/p&gt;

&lt;p&gt;There is no syscall-level network block, which is a real gap and is written down as one. It is acceptable only because &lt;code&gt;node --check&lt;/code&gt; parses without executing the candidate's code, so nothing on this path can make an outbound call. The note in the source says plainly that if a future check needs to actually execute candidate code, resource limits alone are not sufficient and it needs a network namespace or a dedicated low-privilege UID first.&lt;/p&gt;

&lt;p&gt;I am telling you the limits of my own sandbox because you are about to send it your stack traces, and you should know what it does before you do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it
&lt;/h2&gt;

&lt;p&gt;DebugAI runs as an MCP server, so any MCP client can call it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; @debugai/mcp setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browser sign-in, no key to copy. It writes config for every MCP client it finds (Claude Code, Claude Desktop, Cursor, Windsurf, Zed, Gemini CLI, Cline), backs up each file first, then checks the wiring actually works.&lt;/p&gt;

&lt;p&gt;Look before it writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; @debugai/mcp &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove every trace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; @debugai/mcp uninstall
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is also a VS Code extension, which registers the same server, which is why the CLI skips VS Code by default rather than giving you every tool twice.&lt;/p&gt;

&lt;p&gt;Free tier is 10 debugs a day, no card. Pro is $12/mo. A founding rate of $9/mo stays $9 for life and is claimable until September 1, 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually want you to take from this
&lt;/h2&gt;

&lt;p&gt;Not the product. The field.&lt;/p&gt;

&lt;p&gt;If you build anything that hands a developer a suggestion, the useful question is not "how confident is the model". It is "what, if anything, checked this", and the honest answer is frequently "nothing". Say that. A grey "not checked" next to a fix is worth more than a green 92% that means nobody looked.&lt;/p&gt;

&lt;p&gt;I would rather show you a &lt;code&gt;null&lt;/code&gt; than dress a guess as a result.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://debugai.io/blog/nothing-checked-that-fix" rel="noopener noreferrer"&gt;debugai.io&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>debugging</category>
      <category>programming</category>
      <category>developers</category>
    </item>
  </channel>
</rss>
