DEV Community

Cover image for My Local AI Invented 162 Python Packages. I Reran Everything From Scratch, and 161 Came Back.

My Local AI Invented 162 Python Packages. I Reran Everything From Scratch, and 161 Came Back.

Soumyadeep Dey on November 01, 2026

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine I had three small open-weight models on my laptop...
Collapse
 
stackmasteraliza profile image
Aliza Ali •

The 161-of-162 reproducibility is the scariest number here, because it makes the fake names predictable, and predictable names are exactly what slopsquatters register. That also means the guard's hardest case is the day after an attacker publishes one: an "exists on PyPI" check passes. Pairing the existence check with package age and download history (flag anything created recently with near-zero downloads) would keep the guard useful after the phantom becomes real. Great dataset.

Collapse
 
t-rexbytes profile image
Sourish Panda •

Thanks. Agreed, that's the case that matters. The guard already handles it: a name that exists on PyPI still gets blocked as SUSPECT if it's young and barely used (first release in the last 24 months, 3 or fewer releases, under 500 downloads a month), or if it's young and TabPFN scores the name as ghost-like. This isn't hypothetical. Two packages that are already registered match all five of my squat rules, and TabPFN gave both of them about 0.95 before it knew they existed.

One caveat on the 161/162: the rerun used the same models, seeds and temperature, so it shows the results are reproducible, not that the names come back under any settings. Still, 40 names came back across different samples in the original run, so they really are predictable.

Collapse
 
evgeniikonev profile image
Evgenii Konev • • Edited

so it's not random noise, the same fake names come back every run. great for your guard, and also great for whoever squats those names

curious what the agent does after the hook blocks it, does it just make up another name?

PS.
Nice images!

Collapse
 
t-rexbytes profile image
Sourish Panda •

Good point. 40 of the 162 ghost names came back more than once, and every name suggested 3+ times came from more than one model. That's why the public data uses keyed ids and not the names.

When the hook blocks an install, the agent gets the reason: the package isn't on PyPI, and a "Did you mean X?" if there's a close real name. In my demo the agent switched to the real package and didn't invent another one. That's one session, though. I didn't measure how agents recover across many tasks. If one does invent a second fake name, that install gets checked and blocked the same way.

also thanks for the appreciation about the pictures !😁

Collapse
 
evgeniikonev profile image
Evgenii Konev •

Keyed ids instead of names is smart, I didn't think about it but yes, otherwise you give the list to squatters.

"Did you mean X" is the best part I think, agent gets the way out and don't need to guess again. Will be interesting to see the numbers across many tasks, if you will measure it.

Thread Thread
 
t-rexbytes profile image
Sourish Panda •

Before. Low downloads are part of what gets a package flagged, so a brand-new squat with zero downloads is caught right away. If the name isn't registered yet, it's blocked as a ghost. The weak spot comes later: once a package is over 24 months old, it passes, however suspicious the name looks.

The cost is false alarms on real packages that are new or niche. When I replayed every generation through the guard, it blocked 13 of 1,165 real package mentions, about 1.1%. I'd rather get a false alarm on a new package than miss a squat.

Collapse
 
fgrandon profile image
Francisco Grandón •

The finding that 161 of 162 hallucinated package names returned identically on a clean rerun proves package hallucinations are deterministic targets rather than random noise. Placing a TabPFN offline scorer into a Claude Code hook to inspect pip install calls before touching PyPI is a brilliant defense against dependency confusion and slopsquating. How much latency does the TabPFN inference pass add to each agent execution cycle?

Collapse
 
alexcodebytes profile image
Oleksandr •

The '98.7% of outputs were identical word for word' on a rerun is a great argument for open weights. With a closed API, you'd never know if the model changed under you. With open weights, the ghost count is a fact anyone can reproduce on a 4 GB laptop GPU.

Collapse
 
t-rexbytes profile image
Sourish Panda •

Agreed, that's a big reason I used open models. One caveat: the outputs were identical on my laptop, with the same Ollama setup, seeds and temperature. Different hardware or a different Ollama version could change some outputs, so the safe claim is that the ghost count can be reproduced, maybe not every word. 16 of the 1,199 outputs already differed between my two runs.

Collapse
 
alexcodebytes profile image
Oleksandr •

The 'safe claim is that the ghost count can be reproduced, maybe not every word' is exactly the right framing. It's the same principle as any experiment: you're not trying to reproduce every data point, you're trying to reproduce the effect. 161 out of 162 ghosts coming back is the effect.

Collapse
 
robertpaschal profile image
Odinaka Robert Nnamani •

161 of 162 ghosts coming back on a clean rerun is a scary kind of repeatability.

Collapse
 
t-rexbytes profile image
Sourish Panda •

Agreed, that number surprised me too. One caveat: the rerun used the same seeds and temperature, so it shows the run is reproducible more than anything else. The scarier part is that 40 ghost names came back across different samples in the first run. That's why the guard also flags young, low-download packages, not just missing ones.

Collapse
 
robertpaschal profile image
Odinaka Robert Nnamani •

Fair distinction, and the 40 names repeating across different samples is the scarier detail for me. Does that guard catch those young packages before they get any real downloads, or only once they start showing up?

Collapse
 
paullovelace profile image
Paul Lovelace •

161 of 162 coming back is the number that flips it for me — a one-off hallucination is noise, a repeatable one is a shopping list. The offline bit is what I'd actually steal: a guard that phones home defeats the whole point of running local models. Does the 90.1% offline catch rate hold up once the names get weirder than the study tasks, or is that where it starts leaking?

Collapse
 
sherotree profile image
Sherotree •

The 1.7% on everyday tasks versus 30% on obscure ones is the split I'd actually plan around. A holidays script almost never invents a package; a niche file format does, about eighteen times as often. The prompt also forces an install line, so part of that rate is the model inventing a dependency rather than leaving the line out.

Collapse
 
melonape profile image
melonape •

gemma calling 143 of 271 real packages fake is the bit that kills the "just ask the model to confirm it exists" tip that keeps showing up in slopsquatting guides. it's wrong in both directions, so it's not even a safe filter

Collapse
 
kartik-nvjk profile image
Kartik N V J K •

The coding agents I use often add the dependency to pyproject first and then run uv sync, so the ghost name never shows up in the command the hook sees. I saw phantom check covers requirements.txt and pyproject.toml. When the hook gets a bare uv sync or pip install -r, does scan-cmd open the file too?

Collapse
 
mudassirworks profile image
Mudassir Khan •

the 56.5% of ghost names being the task's own words glued together is the line that stuck. it makes the attack surface predictable, anyone can generate the likely ghost names from a public prompt list and register them first. we saw a milder version of this with an agent that invented internal package names out of our own repo vocabulary.

does your hook fail open or closed when the registry lookup times out? on a flaky network that choice decides whether devs keep it switched on.

Collapse
 
alexshev profile image
Alex Shev •

Re-running from scratch is the right move because it turns an interesting claim into a reproducible observation. The next useful artifact is a machine-readable ledger of package name, resolver result, index queried, and timestamp, so later readers can distinguish model invention from registry drift.