DEV Community

Cover image for Two publish-pipeline slug resolution bugs a code review caught before 18 links went dead
MORINAGA
MORINAGA

Posted on

Two publish-pipeline slug resolution bugs a code review caught before 18 links went dead

Last week the cross-publish pipeline had two bugs that would have silently degraded internal links between articles. Neither one would have caused an error. Links would have resolved to plain text — the link text would still be there, but the href would be gone. A reader wouldn't notice until they tried to click through.

Both were caught in a Codex review of the slug resolver in packages/publish/src/links.ts. Here's what they were.

Edge case 1: legacy filenames without a date segment

The resolver uses a regex to extract a slug from a filename:

// Before
/^\d+-\d{4}-\d{2}-\d{2}-(.+)\.md$/
Enter fullscreen mode Exit fullscreen mode

That pattern requires a date segment between the sequence number and the slug. The current filename convention is NN-YYYY-MM-DD-slug.md. But the first 10 articles in this experiment's repo predate that convention — they're numbered without a date:

01-three-sites-experiment.md          ← no date segment
02-2026-05-01-adding-avoid-if-caveats.md  ← has date segment
Enter fullscreen mode Exit fullscreen mode

The legacy filename never matched the regex. The slug resolver returned null for it, and the link rewriter replaced any [text](https://dev.to/morinaga/i-built-3-programmatic-seo-sites-for-25month-using-claude-haiku-heres-the-full-architecture-3pl8) link with just text. Article 01 had 26 inbound links from other articles. All 26 would have been unwrapped silently on the next bulk publish.

Fix: make the date group non-capturing and optional.

// After
/^\d+-(?:\d{4}-\d{2}-\d{2}-)?(.+)\.md$/
Enter fullscreen mode Exit fullscreen mode

The slug for 01-three-sites-experiment.md is now three-sites-experiment, and all 26 links resolve to the Dev.to URL correctly.

Edge case 2: same-run URL references

The slug map — a Map<string, string> from slug to published URL — was built once at the start of a run by scanning published_urls frontmatter across all existing articles. Articles link to each other using /articles/<slug> paths, which the pipeline resolves to real URLs at publish time.

The problem: in a bulk publish run, articles are published sequentially. If article B links to article A, and both are in the same run, the slug map was built before A had a Dev.to URL. The map would return null for A's slug, and B's link to A would degrade to plain text.

The sequence:

1. Run starts. Slug map built from disk. A has no published_urls yet → absent from map.
2. Article A published to Dev.to. URL returned, written to disk.
3. Article B's links rewritten. Map lookup for A's slug → null. Link dropped.
Enter fullscreen mode Exit fullscreen mode

Fix: update the map in memory immediately after each successful Dev.to publish, before the next article processes its links.

// After publishing A to Dev.to:
const devtoUrl = result.url;
slugMap.set(slugFromFilename(basename(article.filePath)), devtoUrl);

// Now when B processes its links, A's slug resolves correctly.
Enter fullscreen mode Exit fullscreen mode

The map is now a live snapshot rather than a pre-run snapshot. The canonical URL chain benefits directly: Dev.to is the preferred platform, so any cross-article reference resolves to the correct primary URL as soon as that URL exists.

Why both bugs were invisible

Neither threw an error. The link rewriter's unresolved-slug path is intentional graceful degradation: an article that links to something not yet published should still be publishable without a dead href. The assumption was that unresolved slugs meant "not published yet." These two edge cases created false unresolved states for articles that were already live.

An article like:

See the [sitemap setup I described earlier](https://dev.to/morinaga/astrojssitemap-generates-sitemap-0xml-not-sitemap-indexxml-on-small-sites-5c7d).
Enter fullscreen mode Exit fullscreen mode

Would silently become:

See the sitemap setup I described earlier.
Enter fullscreen mode Exit fullscreen mode

No exit code 1, no parse failure. Just a sentence that reads correctly and loses its link.

What the tests cover now

links.test.ts now includes cases for both edge cases specifically:

  • A filename with no date segment resolves to the correct slug
  • A slug registered mid-run (after the map was built) resolves in subsequent articles

18/18 tests pass. The test suite runs in CI on every push, so this class of regression is now blocked automatically. The actual verification was checking the slug map against all 98 currently published articles — all resolved, including the legacy 01 article.


Part of an ongoing 6-month experiment running three AI-curated directory sites. The technical claims here are real; this article was AI-assisted.

Top comments (0)