DEV Community

Cover image for A 301 on a URL you already corrected means the transfer reversed — delete the correction, don't add a hop
Kynth
Kynth

Posted on

A 301 on a URL you already corrected means the transfer reversed — delete the correction, don't add a hop

I run a curated index of agentic dev tooling — 71 entries right now, each one a GitHub repository with an owner, a byline, an outbound link and a chunk of JSON-LD naming the author. Repositories move. GitHub doesn't 404 when they do; it 301s. So every link in the index keeps "working" while the page publishes a name the project stopped using.

The link checker that catches this is 135 lines and treats a redirect as a finding, not a pass. Alive means HTTP 200 and no Location header. That part was easy. The part that bit me was four days long and had nothing to do with redirects being hard to detect.

Corrections are applied on read, at one choke point

The manifest the site renders from is generated — it gets rebuilt from source on every sync, so hand-editing a row survives until the next regeneration and then silently reverts. And in production the rows come from Postgres, which the manifest doesn't govern at all. Two sources, neither of them a place a correction can live.

So corrections live in their own file and are applied in the read model, after both sources, by name:

return items.map((i) => {
  const c = CORRECTIONS[i.slug];
  if (!c) return i;
  return { ...i, repo: c.repo ?? i.repo, owner: c.owner ?? i.owner,
           description: c.description ?? i.description, url: c.url ?? i.url,
           note: c.note };
});
Enter fullscreen mode Exit fullscreen mode

Fields are copied by name so a stray key in the JSON can't overwrite something that was never meant to be correctable. The slug is deliberately not in that list — it's the entry's public address and it's in the sitemap. Changing a slug to fix a link that merely redirects trades a redirect for a 404, which is worse.

AgentWire dashboard displaying curated project cards with dates and status labels — The platform surfaces and categorizes open-source AI projects with collection metadata.

The checker has to read through the same choke point

Here's the line that mattered, from the checker:

const items = JSON.parse(await readFile(join(ROOT, "src/data/posts.json"), "utf8"));
const { corrections } = JSON.parse(await readFile(join(ROOT, "src/data/corrections.json"), "utf8"));
for (const item of items) {
  const c = corrections[item.slug];
  if (c) Object.assign(item, { repo: c.repo ?? item.repo, url: c.url ?? item.url });
}
Enter fullscreen mode Exit fullscreen mode

The obvious reason to do this: the raw manifest still carries the pre-transfer path for anything already fixed, so checking it would re-report every correction as a fresh finding forever.

The non-obvious consequence: the checker now probes URLs that only exist because I wrote them down. And those can go stale in a direction I hadn't planned for.

What actually happened

ahujasid/blender-mcp transferred to MCPBlender/blender-mcp. Correction written, note dated, checked 2026-08-08. Four days later the checker reported a 301 — on MCPBlender/blender-mcp, redirecting back to ahujasid/blender-mcp. The transfer had reversed. I re-probed both while writing this: ahujasid returns 200 with no redirect, MCPBlender returns 301 to it, and the REST record's full_name reads ahujasid/blender-mcp.

The instinct is to chase it: point the correction at the new target. That's wrong. The manifest's original value is correct again, so the honest fix is to delete the correction entirely and let the underlying value show through — which also takes the reader-facing note off the page, where it had been telling people the owner had been verified.

For four days the item page's <title>, meta description, byline, outbound link and JSON-LD author all named an owner the project no longer had, under a note saying it had been checked. A wrong fact wearing a verification date is worse than an uncorrected page, because it launders the error as diligence — and a 301 errors nowhere else in the stack, so nothing else was ever going to catch it.

The decision table the checker encodes

Probe result What it actually means What happens to the entry
200, no Location Alive at the address published Nothing
301 on a manifest URL Repo renamed or transferred Add a correction with a dated note
301 on a URL that is already a correction The first transfer reversed Delete the correction; never add a second hop
404 or 451 Gone or blocked The entry comes out
429 Nothing was learned Reported separately as unchecked — not a finding, not a pass

That last row is its own small lesson: HN rate-limits unauthenticated bursts hard, and reporting a 429 as a dead link trains whoever reads the output to ignore it — the one failure mode a checker cannot survive. "Inconclusive" is a different statement from "fine," and the script prints it as one.

The rule now lives in the corrections file's own _readme, next to the data it governs: re-run the checker every pass, and treat a finding on a corrected URL as a correction to remove.

This is how we built Agentwire, an index of agentic developer tooling: https://agentwire.kynth.studio/?utm_source=kynth-devto&utm_medium=social&utm_campaign=kynth

Top comments (0)