Skip to main content
Back to all posts
Oct. 20268 min read

Possessed: your link checker is green, and eight of those links now open a casino

A link checker asks one question: did the server answer? When a domain expires and someone buys it, the answer is still 200. Possessed is a GitHub Action where Gemma 4 reads each passing link against the Internet Archive's capture from the day it was added, and points the possessed ones back at the original. On DEV's 1,000 most-loved posts it found 183 such links.

HacktoberfestDEV ChallengeGemma 4EmbeddingGemmaOllamaGitHub ActionsInternet ArchiveOpen SourceHackathon
I wrote this post for the DEV Hacktoberfest Open-Source AI Challenge, Week 4: Ghost in the Machine (October 2026), and built Possessed for the same submission. Source at github.com/JonathanSolvesProblems/possessed, MIT, running on open-weight models through Ollama with no API key. #hf26challenge
The claim in one sentence: on design-resources-for-developers (67,000 stars), a status-code checker passes every link, while 8 of them open gambling sites today, which anyone can check against the Internet Archive capture linked in each row of the pull request; and on DEV's 1,000 most-loved posts, 183 links in 96 posts open something other than what the author cited, counted only where Gemma 4 and a blind Qwen judge agree or a hand check confirms. The precision figures below are measured against labels that come from those two models plus my own checks, so they are partly self-graded and recall cannot be measured; neither pull request has been merged yet.
Title card on a dark background patterned with faint 200 OK text: a circular headshot above the word Possessed and the line Your link checker is green. Some of those links now open a casino.
Possessed: a GitHub Action where Gemma 4 reads the links your status-code checker already passed.

A link checker asks one question

design-resources-for-developers is a list of links with 67,000 stars. It has a link checker, and the checker passes. Open the React UI Libraries section, click Chakra Templates, and you get a page in Indonesian about which slot games pay out. The domain expired, someone bought it, and the server answers 200, which is the only question a link checker asks. Eight entries on that list open gambling sites today. Another 22 open a domain-for-sale page, an essay mill, a car detailing shop, or some other company that has nothing to do with the words next to the link. None of them fail the checker.

Side by side for chakra-templates.dev: on the left, the Internet Archive capture from 3 June 2022 showing Production-ready Chakra UI Templates for developers; on the right, today's page at mainslotresmi.org, an AGEN338 slot-gambling storefront.
Chakra Templates on a 67,000-star list, archived in June 2022. The same link today opens AGEN338, a slot site.
A terminal running link-check on readme.md: nine URLs, each with a green check and status 200, ending with 9/9 links OK. All checks passed.
What a status-code checker sees: 200 on every possessed link, all checks passed.

Gemma reads the page, then the archive

Possessed runs after your link checker. For every link the checker passed, Gemma 4 opens what the link shows today and the Internet Archive's capture from the day the link was added (git log -S finds that commit), reads both next to the sentence that cites the link, and gives one of four verdicts: fine, moved, possessed, dead. Gemma chooses its own evidence. It has fetch_live, find_snapshot, list_snapshots and read_snapshot, and four verdict tools. A possessed link usually takes it three or four steps: open the live page, find the capture nearest the day the link was added, read it, decide. When the nearest capture is already the parked page, it lists captures and goes back further. The Chakra Templates link took four tool calls and 9.5 seconds on my laptop's GPU.

A terminal running possessed.repo on design-resources-for-developers: 1245 links, the checker passes 1114, and Gemma's tool calls for Chakra Templates (fetch_live landing on mainslotresmi.org, find_snapshot, read_snapshot, then possessed with the archive replacement), beside thumbnails of the 2022 capture and today's slot site.
Gemma 4 picks its own evidence: the live page, then the Internet Archive capture from when the link was added.

Nothing in the pull request is a guess

A verdict that proposes a replacement is refused by code unless the replacement is a page Gemma opened in that session: an archive snapshot it read, or a live URL a link checker would pass. The refusal goes back to the model as a tool error that says what to do instead. The gate started as a prompt instruction, "never invent a URL", and became twenty lines in tools.py, because a rule with an objective test belongs in code. Across 597 judgments on five lists it fired 18 times, and all 18 were my bugs rather than Gemma's: seven were a trailing slash I had stored differently from how the model wrote the URL back, eleven were JavaScript-rendered snapshots my rule mistook for empty pages. Not once did Gemma propose a URL it had not opened. I fixed both bugs, kept the gate and the traces, and re-judged the big list under the fixed gate: zero refusals.

A terminal running the gate test: first, a possessed verdict with an archive URL the model never opened, marked REFUSED by the gate with the tool error sent back; then the same verdict after find_snapshot and read_snapshot open the June 2022 capture, which passes. Beside it, the capture that was opened.
The gate in code: a replacement Gemma never opened is refused. After it reads the capture, the same verdict passes.

Two pull requests you can open

The Action wrote one on its own, on a fork of design-resources-for-developers, from a hosted GitHub runner with no GPU: seven links, six of them gambling sites, each pointed at the archive capture from the month it was added. On the CPU Gemma reads a link in two to three minutes, so the run took 30 minutes end to end on a 1,245-link list, eighteen of them Gemma reading eight links, and the Action has a limit input that takes the strongest signals first. The other went to a maintainer, on vuejs/awesome-vue: six entries, one of them listed in 2018 as a PWA for swapping used video games that today opens a Korean guide to baccarat sites. All six survived the list's dead-link cleanup in January, because they still return 200. For that one I used confirmed mode, which only applies fixes both models agree on.

GitHub pull request #1, Replace links that now open something else, opened by github-actions[bot], with a Possessed links table listing seven links, the date each was added, what it opens today (gambling sites, a domain-for-sale page) and what it was, each marked possessed.
The pull request the Action opened on a hosted runner with no GPU: seven links, six of them gambling sites.
vuejs/awesome-vue pull request #4593, Remove six entries whose links now open something else, with a table of six links, what each opens today and what it was, each removed with a link to the archived original, and a note that every row was checked by Gemma 4 and by Qwen 3.5 4B.
vuejs/awesome-vue #4593: six entries that survived the list's dead-link cleanup, removed with archive evidence.

What each model added

Four detectors were asked the same question about the same 188 links from design-resources: does this link need a fix? A status-code checker flags none of them, by construction. URL heuristics (another domain, a homepage, parking words) flagged 61 and were right on 29, a precision of 0.47. EmbeddingGemma similarity alone flagged 30 and was right on 22, 0.73. Gemma 4 reading the page flagged 67 and was right on 49, 0.73, and on the possessed verdict alone it was right on 30 of 37, where the heuristics were right on 19 of 61. EmbeddingGemma is also what makes the reading affordable: on design-resources it turned 1,114 passing links into 200 reads.

Then I pointed it at DEV

I took the 1,000 most-loved posts on DEV and pulled their 13,788 unique links; a status-code checker passes 11,119 of them as readable pages. Triage picked 2,951 candidates, plus 150 random controls from the links it did not flag, and Gemma 4 judged all 3,101 overnight on my laptop. 145 of the 150 controls were fine, so triage is not hiding much. Qwen re-judged every flagged link, blind. Where both models independently agree and the hand checks confirm it, 183 links in 96 posts, carrying 95,627 reactions between them, now open something other than what the author cited. A React resource collection with 3,438 reactions links js.coach, which today is a Swedish casino directory. A 3,135-reaction list of must-read programming books now serves an Indonesian lottery page.

Results card titled DEV's 1,000 most-loved posts, links that pass a status-code checker but open something else: 183 links, 96 posts, 95,627 reactions, with a note that Gemma 4 judged every candidate and Qwen 3.5 re-judged blind, counted only where both agree or a hand check confirms.
DEV's 1,000 most-loved posts: 183 links in 96 posts, carrying 95,627 reactions, now open something else.

What is not claimed

The precision figures are measured against labels that come from where Gemma and Qwen agree, plus hand checks where they do not, and Claude Code wrote the first pass of those hand labels (the label files say so), so the exam is partly self-graded. A link both models missed is not in the set, so recall cannot be measured this way, and I do not claim it. Agreement is not truth either: spot-checking the top DEV rows with fresh fetches, I found both models wrong together on seven links from one blog whose articles still load behind an asset redirect, and those rows are corrected by hand; the 399 links Gemma flagged but Qwen did not confirm are not in the count at all. Gemma makes known mistakes: a GitHub profile credited as a project's author reads to it as possessed, and a promo banner read as a takeover. The Action by itself applies Gemma's verdicts, which is why every row carries its evidence, and confirmed mode is what I used for a maintainer's list. Pages behind a bot wall and empty JavaScript shells cannot be read without a browser (42 of the 1,114 on design-resources), and Possessed counts them instead of judging them. It cannot tell you a page is right, only that it still matches what the archive saw when you linked it. Neither pull request has been merged, and design-resources-for-developers has outside contributions paused, so its fixes live on a fork.

Related project

Possessed: a GitHub Action where Gemma 4 reads the links your status-code checker already passed, and points the ones that now open a casino back at the Internet Archive's original

View the project