<?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: Muhammad Abdullah Shah</title>
    <description>The latest articles on DEV Community by Muhammad Abdullah Shah (@muhammad_abdullahshah_96).</description>
    <link>https://dev.to/muhammad_abdullahshah_96</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4160112%2F66926732-d2c3-4636-8441-2c3443cb61a0.webp</url>
      <title>DEV Community: Muhammad Abdullah Shah</title>
      <link>https://dev.to/muhammad_abdullahshah_96</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/muhammad_abdullahshah_96"/>
    <language>en</language>
    <item>
      <title>Like Figma, but for debugging: building a tracer two people can share</title>
      <dc:creator>Muhammad Abdullah Shah</dc:creator>
      <pubDate>Sun, 15 Nov 2026 07:54:19 +0000</pubDate>
      <link>https://dev.to/muhammad_abdullahshah_96/like-figma-but-for-debugging-building-a-tracer-two-people-can-share-3ped</link>
      <guid>https://dev.to/muhammad_abdullahshah_96/like-figma-but-for-debugging-building-a-tracer-two-people-can-share-3ped</guid>
      <description>&lt;p&gt;Debugging is a team activity we still do alone. Python Tutor is single user and Python only. Over a call, one person shares a screen while everyone else watches, and nobody can point at the exact line where a variable went wrong.&lt;/p&gt;

&lt;p&gt;I spent a week building Trace to fix that. You open a room with a six character code, no accounts, no sign up. A friend joins with the same code. You both edit the same snippet live, hit Run, and watch the code execute together. A timeline scrubber lets you drag back through the run, with the variables and call stack updating step by step. It is live at trace-swwy.vercel.app.&lt;/p&gt;

&lt;h2&gt;
  
  
  A line event fires before the line runs
&lt;/h2&gt;

&lt;p&gt;Python tracing starts with sys.settrace, and the first thing it taught me is that a line event fires before the line executes. If you snapshot the locals on every line event, you are recording the state before the assignment, not after it. The last line of a function never shows its final values at all, because the function returns without another line event firing.&lt;/p&gt;

&lt;p&gt;I found this on day one, writing fixtures by hand, because the final value of a variable kept coming back one step stale. The fix was to listen for return events too and treat them as the moment a frame's state is complete. It took an afternoon to find, because the wrong output looked almost right.&lt;/p&gt;

&lt;p&gt;The JavaScript side had its own version of the same lesson. There is no settrace in Node, so I parse with acorn, instrument statement boundaries with step callbacks, and run the result in a vm context with no require, no filesystem, and no network. Async code returns a clean "not supported in v1" error instead of a half trace.&lt;/p&gt;

&lt;h2&gt;
  
  
  User code never runs in the API process
&lt;/h2&gt;

&lt;p&gt;One rule before writing any tracer code: user code never runs in the API process. Each run spawns a subprocess with a scrubbed environment, no network, a read only working directory, CPU and memory caps, a ten second wall timeout, and a 2,000 step cap. Anything past the step cap gets flagged as truncated in the UI, never dropped quietly.&lt;/p&gt;

&lt;p&gt;That sounds like paranoia until you remember what the endpoint does. It takes code from a stranger and executes it. The sandbox is the product boundary, not a hardening pass at the end. I wrote the limits as named constants in one config module so tuning is one edit, and I tested the kill paths the same way I tested the happy path.&lt;/p&gt;

&lt;h2&gt;
  
  
  A loop will blow up your trace payload
&lt;/h2&gt;

&lt;p&gt;The third thing that broke was scale, and it broke in the most boring way possible. A 60 iteration loop produced 124 steps. That is fine. A real program with a few thousand iterations is not, because every step carries a snapshot of the locals, the stack, and stdout so far.&lt;/p&gt;

&lt;p&gt;The answer was two rules. First, cap steps and say so plainly. Trace stops at 2,000 steps and tells you, with the option to raise the cap in config. Second, snapshots are JSON safe values with an explicit truncation marker. Objects get a type, a length, and a shallow preview. Nothing is dropped silently. The 60 iteration loop traced correctly, all 124 steps, and the total came out 1770, which I checked by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The co-pilot only knows what the trace contains
&lt;/h2&gt;

&lt;p&gt;The AI co-pilot was the feature I was most nervous about. A debugger that invents an explanation is worse than one that stays quiet. So the co-pilot gets one input, the trace JSON, and its explanations have to cite the step index and line they are talking about. If no Groq key is configured, it answers 503 and everything else keeps working.&lt;/p&gt;

&lt;p&gt;I wrote a seeded bug fixture for this: a function that returns None, then code that indexes into it. Seven steps. The variable is None at step 4, the TypeError lands at step 5. The explanation has to name step 4. That test exists because the failure mode is so quiet. A confident wrong explanation sends a student hunting in the wrong file for an hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do differently
&lt;/h2&gt;

&lt;p&gt;Two things. First, I would have written the sandbox limits and the truncation policy on day one instead of day two. They shaped every later decision, and the tracer code I wrote before them had to be revisited once the limits existed. Second, I would have dogfooded with a real second person sooner. The two browser room test passed locally: collab edit, presence, shared run, zero console errors. The live version, two real people debugging together, is still the next step.&lt;/p&gt;

&lt;p&gt;The backend runs on Render's free tier, and the first visit can take half a minute to wake up. The landing page warns first visitors, and the frontend rides out the wake-up. Free tier trade-offs are fine as long as you name them.&lt;/p&gt;

&lt;p&gt;Trace shipped with 91 backend tests green and the frontend build green. The part I am proudest of is not a feature. It is that the values on screen are honest: what the code actually did, with the gaps labeled instead of hidden. That is the whole job of a debugger.&lt;/p&gt;

&lt;p&gt;Try it live: &lt;a href="https://trace-swwy.vercel.app" rel="noopener noreferrer"&gt;https://trace-swwy.vercel.app&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>My bug reproduction agent trusts the container, not the model</title>
      <dc:creator>Muhammad Abdullah Shah</dc:creator>
      <pubDate>Sun, 08 Nov 2026 07:53:27 +0000</pubDate>
      <link>https://dev.to/muhammad_abdullahshah_96/my-bug-reproduction-agent-trusts-the-container-not-the-model-4he5</link>
      <guid>https://dev.to/muhammad_abdullahshah_96/my-bug-reproduction-agent-trusts-the-container-not-the-model-4he5</guid>
      <description>&lt;p&gt;&lt;em&gt;A quick update since this first went up on AWS Builder Center on Nov 6: Phase 3 passed its gate that same evening. The live fixture run on Qwen3-Coder came back 7 of 10 reproduced with zero setup failures, the malicious-issue drill passed on the host, and Qwen is now the default model. The frontend is live too. The rest of this post is unchanged from the original.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The most expensive sentence in open source maintenance is "can't reproduce." A maintainer reads an issue, tries, fails, and closes it. The reporter did real work writing it up and it dies there. PatchPilot is my attempt at that gap. You paste a GitHub issue link, and the agent reproduces the bug in Docker, then generates a failing test and a fix diff. Version one covers Python, C++, and JavaScript.&lt;/p&gt;

&lt;p&gt;I am writing this mid-build. Phase 3, the reproduction loop, is not at its gate yet, and the live Docker runs are still ahead of me. What follows is what the build has already taught me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The verdict comes from the container, not the model
&lt;/h2&gt;

&lt;p&gt;The first decision I made, back in Phase 0, was deterministic-only verdicts. The model reads the issue and writes a reproduction script, and that is its whole job in the loop. Whether the bug reproduced is decided by what happened inside the container: the exit code, the output, whether the quoted error appeared. The model never gets to declare success.&lt;/p&gt;

&lt;p&gt;Models are agreeable. Give one a broken script and it will find a way to tell you it worked. Anchoring the verdict to something observable removes that failure class, and it makes the verdict debuggable: when a run says reproduced, I can point at the exact output line that earned it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issue text is untrusted input
&lt;/h2&gt;

&lt;p&gt;A GitHub issue is written by a stranger, and the obvious failure is letting the issue text steer what runs. We ran a malicious-issue drill in Phase 3, with issue text crafted to act as a command. It passed because the pipeline never lets issue text near the executor. The issue is data the model reads. The commands that run are built by my code from a fixed template. That separation is the whole defense, and it has to hold by construction.&lt;/p&gt;

&lt;p&gt;I had learned the same lesson on PRISM, where commit messages feed the intent analysis and a poisoned commit could steer the output. Untrusted text enters a pipeline, and somewhere downstream something treats it as instructions. Fence it where it enters, or you will fence it everywhere else forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fixture suite caught the agent lying to itself
&lt;/h2&gt;

&lt;p&gt;Before trusting any reproduction result, I curated 10 real Python issues as fixtures, each verified twice: the quoted error present at the base commit, and gone at the fix commit. That double check caught a real defect in my own code. The agent was cloning HEAD instead of the base commit, so repro runs passed against code that already contained the fix. Every "reproduced" result before that fix was fiction, and it looked green.&lt;/p&gt;

&lt;p&gt;The same work turned up two more defects: boto3 was never pinned or installed, so the Bedrock path would have failed the moment it ran for real, and a torn-read race in the job store, which only shows up when a job is being written and read at the same time. All three came out of fixture curation, not out of staring at the agent code. The fixtures, saved in scripts/fixture_issues.json, now double as the model shootout. The same 10 issues run on Claude Haiku 5.5 and on Qwen3-Coder through Bedrock, and whichever wins becomes the default. Same issues, same verdicts, count the wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cheap checks before expensive ones
&lt;/h2&gt;

&lt;p&gt;Syntax checks run before any container spins up, so a repro script with a syntax error never reaches Docker. It sounds trivial, and it saves real money, because container runs are the slow, expensive step, and most broken scripts are broken in ways a parser catches in milliseconds. The same instinct runs through the whole design. One job at a time, two runs per IP per day, three globally, because an open demo that runs Docker for strangers is a free compute giveaway if you do not cap it first. The ceilings went in during Phase 2, before the reproduction loop existed, and the host is an EC2 box paid for with AWS Activate credits. The bill stays boring because the limits were decided before the first public run.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do differently
&lt;/h2&gt;

&lt;p&gt;Fixtures first. I built the agent, then built the fixtures that proved it wrong, and they should have existed on day one of Phase 3. Without them, every green run is a rumor.&lt;/p&gt;

&lt;p&gt;Second, decide the verdict contract before choosing the model. I settled the model question, Bedrock primary with Haiku against Qwen, after the loop existed, and the contract should have come first. The model is interchangeable. The verdict is the product.&lt;/p&gt;

&lt;p&gt;Third, one thing I got right: the abuse ceilings went in during Phase 2, before the first public run, so I can leave the demo open without watching it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest state of it
&lt;/h2&gt;

&lt;p&gt;Phase 1 and Phase 2 are approved and live. Phase 3's core is built at 79 of 79 tests, ruff clean, CI green. But the gate needs the live runs: deploy the Phase 3 backend to the EC2 host, run the fixture suite with real Docker on Haiku and then on Qwen, exercise every verdict path, and run the malicious-issue drill on the host. Until those runs exist, this is a build log, not a victory lap. The mistakes are fresh, the fixes are testable, and the next person building one of these gets the warnings first.&lt;/p&gt;

&lt;p&gt;The demo is live at &lt;a href="https://patchpilot-shah20.vercel.app" rel="noopener noreferrer"&gt;https://patchpilot-shah20.vercel.app&lt;/a&gt; if you want to poke at it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Production mode caught the join race my dev tests missed</title>
      <dc:creator>Muhammad Abdullah Shah</dc:creator>
      <pubDate>Sun, 01 Nov 2026 07:52:01 +0000</pubDate>
      <link>https://dev.to/muhammad_abdullahshah_96/production-mode-caught-the-join-race-my-dev-tests-missed-4nb2</link>
      <guid>https://dev.to/muhammad_abdullahshah_96/production-mode-caught-the-join-race-my-dev-tests-missed-4nb2</guid>
      <description>&lt;p&gt;Trace is a collaborative code execution visualizer, two people watching the same code run, live. This week I finished its deploy slice. The part that stuck with me was a test I ran in production mode, because that test caught a bug my dev runs had never shown me.&lt;/p&gt;

&lt;p&gt;I opened a room in one browser and joined from another, then ran Python and JS snippets in both. Both sessions got the same trace broadcast, which is exactly what should happen. Then I tried the awkward case, two people joining at the same instant. On the first socket, member_joined could land before hello. That is a confusing first run for a new user. They would see someone else arrive before the room had even said hello to them.&lt;/p&gt;

&lt;p&gt;My dev runs never caught that. Dev mode is forgiving in ways production is not, and the ordering that felt solid locally turned out to depend on timing I had not stressed.&lt;/p&gt;

&lt;p&gt;The fix was small. I added a join lock that serializes join and leave, so hello stays first. The failing run is the red evidence, and the passing rerun plus a regression test that held stable over five runs is the green. I trust that order now because I watched it break first, then watched it hold.&lt;/p&gt;

&lt;p&gt;The same pass caught a quieter leak. Sandbox errors no longer carry driver stderr, so a crash traceback cannot quote user source back out. That one matters more than it sounds. An error message that echoes code back to the wrong session is a small hole, and small holes are the ones that ship unnoticed.&lt;/p&gt;

&lt;p&gt;Rate limiting held under a burst too, with 8 of 25 rapid requests answered 429. The AI co-pilot returned a 503 without a key, as designed, since no key is set yet. The backend suite is 90 of 90 green, up from 87.&lt;/p&gt;

&lt;p&gt;The rest of the deploy slice was quieter work. Render blueprint, backend Dockerfile, env examples, a Render then Vercel deploy guide, and a security checklist pass. All of that is ready.&lt;/p&gt;

&lt;p&gt;I am calling it deploy-ready, not deployed, and that distinction matters. Trace is not live yet. The Render service creation and the Vercel import are still dashboard steps, and the Dockerfile has never been built locally, so the Render build will be its first real Docker test. Ship day is Monday.&lt;/p&gt;

&lt;p&gt;If you build anything with rooms and sockets, run your collab tests against the production build before you call it done. Dev mode flatters you. Production mode argues back, and that argument is worth hearing before your first real users do.&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>I built a debugger two people can watch at the same time</title>
      <dc:creator>Muhammad Abdullah Shah</dc:creator>
      <pubDate>Sun, 25 Oct 2026 07:51:43 +0000</pubDate>
      <link>https://dev.to/muhammad_abdullahshah_96/i-built-a-debugger-two-people-can-watch-at-the-same-time-pmp</link>
      <guid>https://dev.to/muhammad_abdullahshah_96/i-built-a-debugger-two-people-can-watch-at-the-same-time-pmp</guid>
      <description>&lt;h1&gt;
  
  
  I built a debugger two people can watch at the same time
&lt;/h1&gt;

&lt;p&gt;I have lost count of the debugging sessions that went like this. I share my screen, I run the code, and I narrate what it is doing line by line while the other person nods at a video feed. They cannot pause it. They cannot rewind it. They cannot point at a variable and ask what it held two steps ago.&lt;/p&gt;

&lt;p&gt;So I started building Trace. The short version is Figma, but for debugging. Two people join the same run, watch the same code execute live, and scrub back through it on a timeline when something looks wrong.&lt;/p&gt;

&lt;p&gt;I gave it four days before my exams forced a pause. Here is what actually shipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 1: the tracer
&lt;/h2&gt;

&lt;p&gt;A Python tracer built on sys.settrace, a Pydantic trace schema to keep every step honest, and a Next.js scaffold to show it in. Ten commits, 22 of 22 backend tests green. Nothing to look at yet, but the foundation was real.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 2: the runner
&lt;/h2&gt;

&lt;p&gt;A sandboxed runner behind POST /execute, traces retrievable at GET /traces/{id}, and a JavaScript tracer using acorn so it is not a Python-only toy. Five more commits, 60 of 60 tests green.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 3: the multiplayer part
&lt;/h2&gt;

&lt;p&gt;Rooms joined by code. A Yjs relay running inside FastAPI, not a Node sidecar, because one process and one deploy beats babysitting a second service. Presence with remote cursors, and a shared run button so either person can kick off the execution. Ten commits, 73 of 73 tests green.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 4: the part you actually look at
&lt;/h2&gt;

&lt;p&gt;An SVG execution graph with time on the x axis and line number on the y axis. A timeline scrubber with play and pause, 0.5x to 4x speed, and arrow-key stepping. A variables panel (text only, dunders filtered out), a stack panel, and a stdout view. I verified it with Playwright screenshots of a real 10-step loop trace, on desktop and mobile. Still 73 of 73 tests green.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is not built yet
&lt;/h2&gt;

&lt;p&gt;The AI co-pilot that explains each step as it runs. That is the plan, and I am saying so plainly instead of letting the pitch imply it exists. Exams pause the build until October 31, then Days 5 to 7 finish it.&lt;/p&gt;

&lt;p&gt;The part I keep coming back to is the scrubber. Debuggers show you the present. A timeline lets you argue about the past, and when two people are staring at the same bug, the past is usually where the answer lives.&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>nextjs</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I priced my own dead code and got a $775 bill</title>
      <dc:creator>Muhammad Abdullah Shah</dc:creator>
      <pubDate>Sun, 18 Oct 2026 07:51:40 +0000</pubDate>
      <link>https://dev.to/muhammad_abdullahshah_96/i-priced-my-own-dead-code-and-got-a-75-bill-162</link>
      <guid>https://dev.to/muhammad_abdullahshah_96/i-priced-my-own-dead-code-and-got-a-75-bill-162</guid>
      <description>&lt;h1&gt;
  
  
  I priced my own dead code and got a $775 bill
&lt;/h1&gt;

&lt;p&gt;Every developer has a graveyard. Mine was 35 files and 2,317 lines, and I couldn't have told you what half of it did. So I built GhostCode, a dead-code analyzer that reads your AST instead of guessing from text, and the first repo I ran it on was my own.&lt;/p&gt;

&lt;p&gt;Here is what I built, how it works, and what the numbers said.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dead code is a P&amp;amp;L line item, not a vibe
&lt;/h2&gt;

&lt;p&gt;Dead code doesn't just sit there. Someone reads it during onboarding, debugs around it, and keeps it passing CI. That's real time, so I wanted a number: how much does this code cost me per year?&lt;/p&gt;

&lt;p&gt;The crude version I started with: every dead line costs $0.50, scaled by complexity. Crude on purpose. A precise-but-wrong number is worse than a crude-but-directional one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works: four layers
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Ingestion.&lt;/strong&gt; tree-sitter parses Python and JS/TS into real ASTs. No regex guessing, no string-matching for "import". If the parser can't see the symbol, it doesn't count.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Graph.&lt;/strong&gt; Everything becomes a NetworkX graph: files, functions, classes, imports. A "ghost node" is any node with zero incoming edges. Nothing calls it, nothing imports it, nothing references it. That's dead code by construction, not by opinion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Scorers.&lt;/strong&gt; Three of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dead-code scorer: how confident are we this is actually dead (deletion confidence)?&lt;/li&gt;
&lt;li&gt;Debt scorer: how tangled is the code that IS alive? Circular imports, complexity hotspots.&lt;/li&gt;
&lt;li&gt;Cost scorer: dead LOC x $0.50, scaled by cyclomatic complexity. A simple dead utility is cheap. Dead code wrapped in nested conditionals that future-me has to read is expensive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Dashboard + auto-PR.&lt;/strong&gt; A Next.js dashboard over a FastAPI API (Docker, deployed on Render), and PyGithub opens the deletion PRs for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dogfooding: my own repo first
&lt;/h2&gt;

&lt;p&gt;35 files, 2,317 lines. The scan found 10 ghost files and 21 ghost symbols. Three circular import chains I didn't know about. Total: &lt;strong&gt;$775.49 of annual maintenance burn&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Some of it stung. A helper module I was "keeping in case I need it later" turned out to have zero incoming edges from day one. It was never needed. Deleting it took one PR and the tests stayed green, 91 out of 91.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell you before you build this yourself
&lt;/h2&gt;

&lt;p&gt;Start with tree-sitter, not regex. Regex-based "unused code" tools lie to you the moment a dynamic import shows up.&lt;/p&gt;

&lt;p&gt;Zero incoming edges is the right definition. It's falsifiable: show me one caller, or it's dead.&lt;/p&gt;

&lt;p&gt;Price it crudely early. The $0.50 number is wrong, but it turned "should I clean this up" into "this file costs $120 a year to keep." That's a decision, not a feeling.&lt;/p&gt;

&lt;p&gt;My repo now has 10 fewer files. The analyzer runs in CI now, so the graveyard doesn't grow back.&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>My first open-source PR got closed, and the maintainer was right</title>
      <dc:creator>Muhammad Abdullah Shah</dc:creator>
      <pubDate>Sun, 11 Oct 2026 07:51:57 +0000</pubDate>
      <link>https://dev.to/muhammad_abdullahshah_96/my-first-open-source-pr-got-closed-and-the-maintainer-was-right-fh3</link>
      <guid>https://dev.to/muhammad_abdullahshah_96/my-first-open-source-pr-got-closed-and-the-maintainer-was-right-fh3</guid>
      <description>&lt;p&gt;This week I did something I had been putting off for years: I fixed two real bugs in open-source projects and opened the PRs myself.&lt;/p&gt;

&lt;p&gt;The two were repowise-dev/repowise#3251 and sktime/sktime#11437.&lt;/p&gt;

&lt;p&gt;The repowise bug: &lt;code&gt;doc_anchors&lt;/code&gt; only registered the first anchor when a document had repeated headings. GitHub-style suffixed anchors (&lt;code&gt;slug-1&lt;/code&gt;, &lt;code&gt;slug-2&lt;/code&gt;) were just missing. The sktime bug: &lt;code&gt;LagLlamaForecaster._update&lt;/code&gt; was loading the entire series history on every update instead of keeping just &lt;code&gt;context_length&lt;/code&gt; rows. Unbounded memory growth hiding inside a method nobody looks at twice.&lt;/p&gt;

&lt;p&gt;Both fixes were small. Both took longer than I expected, because the real work was reading the codebase long enough to be sure I was not breaking something else. The repowise fix needed 2 regression tests to prove the anchors were right. The sktime fix needed 5 direct cases. Ruff clean, existing tests still green.&lt;/p&gt;

&lt;p&gt;Here is the part I did not expect. This morning the repowise maintainer closed my PR without merging it.&lt;/p&gt;

&lt;p&gt;The reason was fair, and I will give it to him straight. Another contributor had claimed the issue in the thread first, and their version handles an edge case mine missed: a heading that literally reads &lt;code&gt;## Usage 1&lt;/code&gt; written out. With a plain counter, that collides with the auto-generated &lt;code&gt;usage-1&lt;/code&gt;. Their version gives the third &lt;code&gt;## Usage&lt;/code&gt; a proper &lt;code&gt;usage-2&lt;/code&gt;. Mine would have shipped a subtle duplicate-anchor bug. So, yes, closing mine was the right call.&lt;/p&gt;

&lt;p&gt;And then the maintainer did something I will remember: he personally pointed me at another issue, #3283, and said it was mine if I claimed it. Importing one helper from &lt;code&gt;ingestion.git_indexer&lt;/code&gt; drags in the whole package and all of GitPython. The fix is lazy imports. I have already written it in my fork, tests pass, and it is waiting on me to claim the issue and open the PR. The sktime PR is still open, awaiting review.&lt;/p&gt;

&lt;p&gt;Two lessons I keep turning over.&lt;/p&gt;

&lt;p&gt;First: writing the fix is the easy half. The other half is the process around it. Claim the issue in the thread before you start, read how maintainers respond, and treat every edge case in a review comment as something you should have thought of first.&lt;/p&gt;

&lt;p&gt;Second: a closed PR is not a failed PR. Mine found the bug, confirmed the fix shape, and got a maintainer to hand me a better issue. That is a net win by any measure that matters.&lt;/p&gt;

&lt;p&gt;If you are thinking about starting: pick a project you already use, look at its good-first-issues, and read more code than you write. The reading is the work.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>beginners</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I built a merge-conflict predictor that ignores lines and reads the AST instead</title>
      <dc:creator>Muhammad Abdullah Shah</dc:creator>
      <pubDate>Sun, 04 Oct 2026 07:51:35 +0000</pubDate>
      <link>https://dev.to/muhammad_abdullahshah_96/i-built-a-merge-conflict-predictor-that-ignores-lines-and-reads-the-ast-instead-38fh</link>
      <guid>https://dev.to/muhammad_abdullahshah_96/i-built-a-merge-conflict-predictor-that-ignores-lines-and-reads-the-ast-instead-38fh</guid>
      <description>&lt;p&gt;Git's idea of a merge conflict is narrower than it should be. Two branches change the same lines, you get a conflict. Two branches touch the same function from different files and never collide, git merges cleanly and congratulates you. Then production breaks.&lt;/p&gt;

&lt;p&gt;That's the merge I've learned to fear most: the clean one. One person refactors a function while another adds a call to it, the merge goes through without a peep, and you find out a week later from a bug report.&lt;/p&gt;

&lt;p&gt;I'm building PRISM, an AI-powered git time machine, and this week I shipped a conflict predictor built around a simple idea: lines are the wrong unit. Symbols are better.&lt;/p&gt;

&lt;h2&gt;
  
  
  The approach
&lt;/h2&gt;

&lt;p&gt;Parse the merge-base and both branch heads into ASTs with tree-sitter, extract the symbols (functions, classes, methods), and flag overlap between the two sides. Both branches touching the same symbol is risk, even across different files. The score saturates so one hot file can't dominate the whole prediction. It's exposed as POST /repos/{id}/predict-conflict, and there's a branch-compare UI with a risk bar and the overlapping symbols listed, so the number is inspectable instead of magic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unglamorous parts took most of the time
&lt;/h2&gt;

&lt;p&gt;Tree-sitter grammar packages aren't always installed on the machine. The first version crashed when one was missing, which made it a demo, not a tool. Now the service degrades to line-level scoring when a grammar is missing. Boring engineering, but it's the difference between something you can actually run and something you can't.&lt;/p&gt;

&lt;p&gt;Method detection inside class bodies broke twice before my tree-sitter queries finally caught it. Queries for methods are fiddlier than function queries, and I learned that through two rounds of debugging. Skill issue on my part, honestly.&lt;/p&gt;

&lt;p&gt;9 new tests, 39 passing overall.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it's actually for
&lt;/h2&gt;

&lt;p&gt;The predictor doesn't replace tests or review. It answers one narrow question: did these two branches step on the same symbols? That's cheap to answer, and it catches exactly the class of breakage that git itself can't see. Next up I'm working on semantic code ownership with embeddings, which should catch the cases where even the symbol names diverge.&lt;/p&gt;

&lt;p&gt;If you merge a lot, you know the feeling of a clean merge that wasn't. What's the worst one you've shipped?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
