Cambiando idioma
Carlos José Castro GalanteCarlos José Castro Galante
Carlos José Castro Galante
© 2026
Projects
AI

Linkmortem

An open source agent that finds the dead links and dead sections in a website or in the docs of a GitHub repository, explains why each one died, recovers what the page said from the Internet Archive, finds where the content lives now and writes a patch with only the replacements it can prove. Code decides everything it can verify, and Gemma 4, an open weights model, reads only the pages code can't judge. It runs at linkmortem.tech, from the command line and as a GitHub Action that opens a pull request and keeps an issue with the report, without ever merging on its own. Submitted to the Hacktoberfest Open Source AI Challenge on DEV, where the full story of the project is told.

Linkmortem

Metrics

Model
Gemma 4
Links settled without the model
90%
Costly errors of the production model
0
Open models compared
9
Automated tests
243
Challenge
Hacktoberfest DEV 2026
Screenshots

The problem

The web keeps losing its memory. 38 percent of the pages that existed in 2013 were gone ten years later, and awesome lists, READMEs and documentation slowly fill up with links that lead nowhere. Common link checkers only look at the status code, so they call a parked domain or a renamed section alive, and they never say what the lost page said or where it lives now. Fixing them by hand means checking every link, finding an archived copy and confirming the replacement is the same content.

The solution

Linkmortem performs an autopsy on every link in six stages. It finds up to 150 links, diagnoses each one down to the section it points to, recovers the last healthy copy from the Wayback Machine, looks for the content's new home, writes the patch and preserves living links that were never archived. Code settles status codes, parked domains, bot checks, Markdown anchors built the way GitHub builds them and moves announced by the archived page itself. Gemma 4 only decides whether a page that answers is alive and whether a candidate is the same page, and without evidence a link is never declared dead. MongoDB Atlas stores shared autopsies and a graveyard of resolved links searched with Vector Search, SerpApi searches live within a daily budget and Sentry traces every scan as an agent.

Technical stack

Frontend
React · TypeScript · Vite · Tailwind CSS · Three.js
Backend
Python · FastAPI · Pydantic · Gemma 4 · Ollama · Backboard · MongoDB Atlas · SerpApi · Sentry · Docker · Render

Challenges

  • Discovering that two models scoring 0 percent in the first evaluation had never answered, because the provider returned an error inside a successful response, and asking for JSON in the prompt to take Llama from 0 to 93 percent

  • Detecting dead sections, the most common break in awesome lists, by building each Markdown file's anchors the way GitHub does, with 427 links to sections across seven lists and no false ghosts

  • Noticing that Enzyme's archived copy announced its move while the search failed, and reading those announcements before searching to find the new home with no model and no search spent

  • Designing a bot that knows when to stay quiet, with one pull request that updates, whole link replacements that never touch code and no action on branches or pull requests a person touched

  • Closing security gaps such as addresses with credentials, invalid ports, a rate limit that a forged header could dodge and requests with no size limit

Learnings

  • Letting the model propose while code decides made the agent trustworthy, and almost every bug ended up moving a decision from the model into code

  • A number without evidence is worth nothing, and the evaluation only became useful once it recorded the reason behind every invalid answer

  • Measuring open models on your own cases lets you choose by costly errors and not only by accuracy

  • Honesty is a product feature, and saying how much was checked and what couldn't be confirmed matters as much as finding ghosts

  • An agent's traces tell what tests don't show, which is where the false 0 percent and a span with no input came from