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.

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.


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.

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.

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.](/projectImages/possessed_06_action_pr.png)

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.

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.
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 projectRelated reading
Spare Thumbs: Gemma 4 answers "can we move the meeting?" in my words, from my real calendar, and never double-books me
Clients move meetings at night. Spare Thumbs is Gemma 4 on my laptop: it reads the request in Gmail, checks my real Google Calendar, drafts the reply in my own words and moves the event once the client confirms. On ten requests against a busy week, plain Gemma 4 offered a booked time in 7 drafts; Spare Thumbs in 0.

A $1.35 fine-tune taught an open 4B model that CRA is ARC in French. It still writes NIS for SIN.
An open 4B model translated "a business number from the CRA" and wrote RRCQ, an agency that does not exist. I fine-tuned it on Tinker for about $1.35 on Government of Canada translations. On held-out canada.ca pages it now writes the government's own French acronym 66.9% of the time, against 50.8%. Every flag it raises cites its source, and it runs on my laptop.
