Skip to content

DOC-6939 Make the page-level id unique - #3761

Merged
andy-stark-redis merged 1 commit into
mainfrom
DOC-6939-unique-page-id
Aug 6, 2026
Merged

DOC-6939 Make the page-level id unique#3761
andy-stark-redis merged 1 commit into
mainfrom
DOC-6939-unique-page-id

Conversation

@andy-stark-redis

@andy-stark-redis andy-stark-redis commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Makes the page-level id unique — item A4 of DOC-6939.

The defect

id was the content file's base name, so it was never unique. 5,733 pages shared 2,161 ids: message_history named 58 different pages, install 42, cli 38. In the published feed that's 2,643 pages under 2,119 ids, so a consumer treating id as a primary key silently lost about 500 pages, with nothing to notice it by.

The applied AI team hit this and worked around it by keying on url. They've since confirmed they're happy to keep keying on url provided the id is genuinely unique — which turns this from a decision into an implementation.

The fix

id is now the content path with the extension and any trailing /_index removed:

Page id
content/commands/set.md commands/set
content/develop/clients/redis-py.md develop/clients/redis-py
content/develop/clients/_index.md develop/clients

Unique by construction — two files can't share a path — and stable across builds, because nothing about it depends on processing order.

Before After
Distinct ids 2,161 5,733
Colliding ids 584, across 4,156 pages 0
children[] entries resolving to a page id 5,717 / 5,717

Section ids, example ids and content_hash are unaffected.

Two alternatives rejected

Dedup suffixes (-1, -2) would have been a smaller change, but which page gets the plain id and which gets the suffix depends on processing order — so an id could migrate between pages between builds and silently repoint anyone who had stored one. An unstable id is worse than a duplicate: a duplicate fails loudly the first time you compare counts, a migrating id doesn't fail at all.

Flattening slashes to hyphens, for a more conventional-looking id, reintroduces collisions — a/b-c and a-b/c both become a-b-c.

Note on structure

The derivation now lives in one partial, layouts/partials/page-id.html. It was previously written out three times — single.json, section.json, and section.json's children[] loop — and "same rule implemented twice, then drifted" has been the recurring theme of this ticket. children[].id in particular must equal the child page's own id or the navigation graph doesn't resolve.

What a reviewer should focus on

One gotcha, recorded because the tests didn't catch it

I first wrote the path normalisation as .Path | replace "\\" "/". In Hugo, piping puts the piped value last, so that evaluates as replace "\\" "/" .Path — the backslash became the input and every id came out as a single backslash. The build passed, and the children[] cross-reference check reported 5,717/5,717 resolving, because everything resolved to the same wrong value. Only printing actual ids caught it. Worth knowing that a cross-reference check can be fully satisfied by uniformly wrong data.

🤖 Generated with Claude Code


Note

Medium Risk
Consumer-visible breaking change to id on all documentation JSON records; correctness improves but any client still keying on the old basename ids must migrate (known consumer uses url).

Overview
Fixes duplicate id values in AI/RAG JSON output by replacing filename-based ids with the page’s content path (extension and trailing /_index stripped), e.g. develop/clients/redis-py.

Adds shared partial layouts/partials/page-id.html and wires single.json, section.json, and children[].id through it so section navigation ids match each child page’s own record. Pages without a backing file still use a urlized title fallback.

Updates content/ai-agent-resources.md so the documented id field matches the new semantics. Every page’s id changes in the published feed—not only previously colliding ones.

Reviewed by Cursor Bugbot for commit f090ac6. Bugbot is set up for automated code reviews on this repo. Configure here.

The record id was the content file's base name, so it was never unique: 5,733
pages shared 2,161 ids, with "message_history" naming 58 different pages and
"install" 42. In the published feed that is 2,643 pages under 2,119 ids, so a
consumer treating id as a primary key silently lost about 500 pages with nothing
to notice it by. The applied AI team hit this and worked around it by keying on
url; they have since confirmed they are content to keep keying on url provided the
id is actually unique, which makes this an implementation rather than a decision.

It is now the content path with the extension and any trailing /_index removed, so
develop/clients/redis-py.md becomes develop/clients/redis-py. Unique by
construction, because two files cannot share a path, and stable across builds
because nothing about it depends on build order.

A dedup suffix was rejected for that second reason. Appending -1 and -2 to
collisions would have been a smaller change, but which page got the plain id and
which got the suffix would depend on the order pages were processed, so an id
could migrate between pages between builds and quietly repoint anyone who had
stored one. An id that is not stable is barely better than an id that is not
unique.

Slashes are kept rather than flattened to hyphens, because flattening reintroduces
a collision risk -- "a/b-c" and "a-b/c" both becoming "a-b-c" -- for no gain, and
the path form matches how the url is already structured.

The derivation lives in one partial rather than being written out in each template.
It was previously duplicated three times, in single.json, section.json and
section.json's children[] loop, and that pattern of the same rule implemented twice
and then drifting has been the recurring theme of this whole ticket. children[].id
in particular has to equal the child page's own id or the navigation graph does not
resolve.

One template gotcha, which the verification caught and reasoning would not have:
writing the path normalisation as ".Path | replace \"\\\\\" \"/\"" makes .Path the
LAST argument, so the backslash becomes the input string and every id on every page
came out as a single backslash. The build succeeded and all 5,717 children[] entries
still "resolved", because they all resolved to the same broken value. Only checking
the actual ids showed it.

5,733 pages now carry 5,733 distinct ids with no collisions, all 5,717 children[]
entries resolve to a real page id, and the section-id, example-id and content_hash
invariants are unchanged.

Learned: in a Hugo template, piping into replace puts the piped value LAST, so ".Path | replace old new" silently uses the pattern as the input -- every id became a single backslash, the build passed, and the children[] cross-reference check still showed 100% because everything resolved to the same wrong value
Constraint: the page id must stay derivable from the content path alone, never from build order -- a dedup suffix would let an id migrate between pages between builds, which is worse than a duplicate because a stored reference silently repoints
Constraint: single.json, section.json and its children[] loop must all take the id from layouts/partials/page-id.html, because children[].id has to equal the child page's own id for the navigation graph to resolve
Rejected: suffixing colliding ids with -1 and -2 | smaller change, but assignment depends on processing order so ids are no longer stable across builds
Rejected: flattening the path to hyphens for a more conventional-looking id | reintroduces collisions, since "a/b-c" and "a-b/c" both become "a-b-c"
Ticket: DOC-6939
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

DOC-6939

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

🧠 Redis Memory

Found 5 related items from repository history:

Memory updated at f090ac6

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@andy-stark-redis
andy-stark-redis requested a review from a team August 6, 2026 13:37
@andy-stark-redis andy-stark-redis self-assigned this Aug 6, 2026
@andy-stark-redis andy-stark-redis added infrastructure AI-friendliness Features that help AI tools understand and use the doc pages more easily. labels Aug 6, 2026

@dwdougherty dwdougherty left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@andy-stark-redis
andy-stark-redis merged commit dc0594a into main Aug 6, 2026
89 checks passed
@andy-stark-redis
andy-stark-redis deleted the DOC-6939-unique-page-id branch August 6, 2026 14:41
@andy-stark-redis

Copy link
Copy Markdown
Contributor Author

Thanks @dwdougherty !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-friendliness Features that help AI tools understand and use the doc pages more easily. infrastructure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants