<?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: Zain Ali</title>
    <description>The latest articles on DEV Community by Zain Ali (@zain_534102).</description>
    <link>https://dev.to/zain_534102</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%2F4196931%2F33542674-40ee-408c-acb7-51bb6c8a23a1.png</url>
      <title>DEV Community: Zain Ali</title>
      <link>https://dev.to/zain_534102</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zain_534102"/>
    <language>en</language>
    <item>
      <title>I found bugs in LiteLLM and Prism by reading their git history</title>
      <dc:creator>Zain Ali</dc:creator>
      <pubDate>Tue, 20 Oct 2026 21:31:23 +0000</pubDate>
      <link>https://dev.to/zain_534102/i-found-bugs-in-litellm-and-prism-by-reading-their-git-history-13i2</link>
      <guid>https://dev.to/zain_534102/i-found-bugs-in-litellm-and-prism-by-reading-their-git-history-13i2</guid>
      <description>&lt;p&gt;A few weeks ago I was reading the source of &lt;a href="https://github.com/prism-php/prism" rel="noopener noreferrer"&gt;Prism&lt;/a&gt;, the Laravel package for talking to LLM providers. It has ten provider integrations side by side: OpenAI, Anthropic, Groq, Mistral, DeepSeek and so on. Each one has a &lt;code&gt;ToolMap.php&lt;/code&gt; that turns your tool definitions into whatever that provider's API expects.&lt;/p&gt;

&lt;p&gt;I noticed that a commit called &lt;a href="https://github.com/prism-php/prism/commit/9447c71d8697950ff1005f2409b95c99afcdcf84" rel="noopener noreferrer"&gt;&lt;code&gt;9447c71&lt;/code&gt;&lt;/a&gt; had fixed a crash in the DeepSeek and OpenRouter tool maps. If a tool had no parameters, they used to send &lt;code&gt;"properties": []&lt;/code&gt;, an empty JSON array, and the APIs rejected it because they want an object. The fix was a couple of lines.&lt;/p&gt;

&lt;p&gt;Then I opened the Groq tool map. Same code, still broken. Mistral, xAI and Z too. The fix had reached two of the ten providers and stopped.&lt;/p&gt;

&lt;p&gt;I sent &lt;a href="https://github.com/prism-php/prism/pull/1038" rel="noopener noreferrer"&gt;a PR for the other four&lt;/a&gt;. But the thing that stuck with me was how ordinary this bug was. Nobody did anything wrong. Someone hit the problem with DeepSeek, fixed DeepSeek, and moved on. The code that needed the same fix was in a different folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;Most codebases have siblings. Provider adapters, database drivers, cache stores, mail transports, payment gateways, platform ports. They all do the same job their own way, and they're usually written by copying the previous one.&lt;/p&gt;

&lt;p&gt;When a bug turns up in one sibling, there's a good chance the others have it too. And the fix is already written down in your git history. Someone just has to go and look, and that's the part I wanted a tool for.&lt;/p&gt;

&lt;h2&gt;
  
  
  half-fixed
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/heyzainali/half-fixed" rel="noopener noreferrer"&gt;half-fixed&lt;/a&gt; reads your git history and finds changes that reached some siblings but not the others.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6divznzx8supe6xnk266.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6divznzx8supe6xnk266.gif" alt="half-fixed scanning prism-php/prism and finding the Groq, Mistral, xAI and Z tool maps that never got the fix"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First it groups files that sit at the same path in parallel folders, like &lt;code&gt;src/Providers/*/Maps/ToolMap.php&lt;/code&gt;. Then it walks the history for commits that touched some members of a group and pulls out what each one added or removed. For every sibling a commit didn't touch, it looks for the old code. If the old code is still there, that's a finding.&lt;/p&gt;

&lt;p&gt;For Prism it reports this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hf-5505660  high  fixed in 2 of 10, still old in 4
  fixed    DeepSeek, OpenRouter 9447c71 2025-10-14 fix(DeepSeek|OpenRouter): ...
  missing  Groq:21, Mistral:21, XAI:21, Z:21
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It only runs &lt;code&gt;git&lt;/code&gt;, with no network calls and no dependencies, and there's no AI in the scanner itself. It read 5,000 LiteLLM commits in 69 seconds on my laptop. Try it on any repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx half-fixed scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it found
&lt;/h2&gt;

&lt;p&gt;I ran it on six projects that keep implementations side by side: Prism, Laravel, Django, Rails, LiteLLM and Knex. The &lt;a href="https://github.com/heyzainali/half-fixed/blob/main/docs/evidence/README.md" rel="noopener noreferrer"&gt;full results are here&lt;/a&gt;, with a link to the exact commit and line for every claim.&lt;/p&gt;

&lt;p&gt;LiteLLM was the interesting one. It has over 100 LLM providers and 50 guardrails, which is a lot of siblings. The scan gave 66 high-confidence findings, and I went through every one by hand: read the original commit, read the sibling, check the provider's docs where it mattered.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;Findings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Real gap&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Obsolete&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Handled elsewhere&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intentional&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unsure&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 13 gaps come down to 11 distinct bugs. Two of them I confirmed end to end on a running proxy and sent as PRs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/BerriAI/litellm/pull/44925" rel="noopener noreferrer"&gt;#44925&lt;/a&gt;: an earlier change made &lt;code&gt;disable_custom_api_keys&lt;/code&gt; read from &lt;code&gt;config.yaml&lt;/code&gt;. The setting right next to it, &lt;code&gt;scope_user_search_to_org&lt;/code&gt;, didn't get the same change. If you set it in &lt;code&gt;config.yaml&lt;/code&gt;, the settings API said it was on, but internal users could still search every user on the proxy.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/BerriAI/litellm/pull/44936" rel="noopener noreferrer"&gt;#44936&lt;/a&gt;: an earlier fix made the Azure passthrough check the &lt;em&gt;value&lt;/em&gt; of &lt;code&gt;stream&lt;/code&gt;. The vLLM passthrough still only checked that the key existed, so a request with &lt;code&gt;"stream": false&lt;/code&gt; got a streaming wrapper and the proxy answered with a 500.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Most findings aren't bugs
&lt;/h2&gt;

&lt;p&gt;Look at that table again. 13 real gaps out of 66. In Laravel, Django and Rails, every finding I checked was either deliberate or harmless: the deprecated Redis cache store in Rails uses a different gem, the old SES transport in Laravel calls an API where the parameter really is called &lt;code&gt;Tags&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In mature, well-reviewed projects, most differences between siblings are on purpose. That's why half-fixed doesn't fix anything automatically. The scanner turns thousands of commits into a short list worth reading. Reading it is the part that needs judgement.&lt;/p&gt;

&lt;p&gt;For that part, half-fixed also ships as an Agent Skill for Claude Code and Codex. The skill tells the agent how to triage each finding: read the original commit, read the sibling, decide whether the change applies, and back the verdict with evidence. If it's a real gap, it writes the fix with a test. A second skill writes the pull request the way that project's maintainers expect, by reading their recent merged PRs first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Claude Code&lt;/span&gt;
/plugin marketplace add heyzainali/half-fixed
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;half-fixed@half-fixed

&lt;span class="c"&gt;# Codex&lt;/span&gt;
codex plugin marketplace add heyzainali/half-fixed
codex plugin add half-fixed@half-fixed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also run it on your own branch before you open a PR. &lt;code&gt;npx half-fixed scan --base origin/main&lt;/code&gt; only looks at your commits, so it catches the case where you fixed two siblings and forgot the third.&lt;/p&gt;

&lt;h2&gt;
  
  
  What doesn't work yet
&lt;/h2&gt;

&lt;p&gt;It groups siblings by file path, so it only works when they live in parallel folders. Knex names its dialect files &lt;code&gt;mysql-querycompiler.js&lt;/code&gt; and &lt;code&gt;pg-querycompiler.js&lt;/code&gt; in the same folder, and half-fixed finds almost nothing there. It also compares lines of text, so if a sibling was refactored heavily it can miss the match.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The code is MIT licensed: &lt;a href="https://github.com/heyzainali/half-fixed" rel="noopener noreferrer"&gt;github.com/heyzainali/half-fixed&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run it on your own code, tell me what it gets wrong. False positives and missed gaps are the most useful thing I can hear about right now.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>git</category>
      <category>ai</category>
      <category>php</category>
    </item>
  </channel>
</rss>
