Skip to content

Fix slugifyWithCounter() returning the same slug twice - #82

Merged
sindresorhus merged 1 commit into
sindresorhus:mainfrom
MFA-G:fix/counter-duplicate-slugs
Sep 9, 2026
Merged

sindresorhus merged 1 commit into
sindresorhus:mainfrom
MFA-G:fix/counter-duplicate-slugs

Conversation

@MFA-G

@MFA-G MFA-G commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

slugifyWithCounter() can return the same slug twice. Its whole purpose is to guarantee unique slugs, so this defeats the feature.

import {slugifyWithCounter} from '@sindresorhus/slugify';

const slugify = slugifyWithCounter();

slugify('foo');    //=> 'foo'
slugify('foo');    //=> 'foo-2'
slugify('foo 2');  //=> 'foo-2'   ← duplicate

The counter is keyed on the incoming slug only (occurrences), so it has no idea that foo-2 was already handed out as the counter form of a different input. It happens in either order — foo, foo 2, foo also yields foo-2 twice — and is easy to hit in practice, since heading-to-anchor generation is the main use case and documents that repeat a heading and separately have a Foo 2 heading are ordinary.

Fix

Track the slugs actually returned in a Set and keep bumping the counter while the candidate is already taken. The comparison is lowercased to match how occurrences is keyed, so a {lowercase: false} call cannot collide with a lowercase one either. reset() clears it alongside occurrences.

The common path is unchanged: the loop body only runs on an actual collision, so there is no extra work for the normal case beyond one Set lookup and insert.

Results after the fix:

slugify('foo');    //=> 'foo'
slugify('foo');    //=> 'foo-2'
slugify('foo 2');  //=> 'foo-2-2'

Validation

  • npm test (xo + ava): 25/25 pass, no lint errors.
  • The new test fails on main with 'foo-2' where 'foo-2-2' is expected.
  • Randomized check over 2000 sessions × 12 calls from a pool of colliding inputs: main produces duplicates, the patched version never does.

@sindresorhus
sindresorhus merged commit 6d97501 into sindresorhus:main Sep 9, 2026
2 checks passed
hyperbola-bear added a commit to hyperbola-bear/claude-mods that referenced this pull request Oct 7, 2026
- a review is asked for by "review" with a PR link, a link alone,
  "review pr", "review pr 418" or "pr review"; the PR is checked out into
  a review worktree (~/.cache/pr-review-ui/review/) whose HEAD sits at the
  merge base, so editors, git diff and the pane show the PR's changes;
  the person's checkout is never touched
- point 1's code shows with nothing to click: the editor (VS Code and
  forks, JetBrains, Zed, Neovim), terminal-browser, or the code pane,
  which docks from 144 columns and otherwise draws the code under the
  open point until the terminal is widened
- one review standard: verdict line, severity tags, area, Problem /
  Impact / Fix, a suggested diff, and a merge plan; a review with no
  points still draws its verdict and summary
- the pane draws the change like git diff: old and new line numbers,
  changed words, more lines on demand, lines fit to the width
- the band and the pane step points with a and d
- the first session after install asks before installing the GitHub
  CLI, Ghostty or terminal-browser; /review-setup asks again
- cache clones are full: Claude Code runs git with lazy fetching off

Checked live in Ghostty against Claude Code 2.1.292, including a real
review of sindresorhus/slugify#82 through the worktree.

Co-authored-by: clement <clement@clements-MacBook-Pro.local>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants