Skip to content

DOC-6951 Date each redirect from git history, and say why deletions are not published - #3775

Merged
andy-stark-redis merged 3 commits into
mainfrom
DOC-6951-page-moves
Aug 11, 2026
Merged

DOC-6951 Date each redirect from git history, and say why deletions are not published#3775
andy-stark-redis merged 3 commits into
mainfrom
DOC-6951-page-moves

Conversation

@andy-stark-redis

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

Copy link
Copy Markdown
Contributor

Item D3 of DOC-6951 — the last item. Half built, half deliberately not, and the half I didn't build is the more interesting result.

Stacked on #3771. Based on DOC-6951-publish-redirects, which it extends. Merge #3771 first.

Built: a date on every redirect we can date

The map is rendered from .Aliases, which is everything Hugo knows — and Hugo can't say when a page moved, because frontmatter doesn't record it. git does.

build/generate_page_moves.py writes those dates into data/page-moves.json before the build, and the template attaches moved_on to each entry it can date: 570 of 1,061.

{"from": "/develop/ai/langcache", "to": "https://redis.io/docs/latest/develop/ai/context-engine/langcache/", "moved_on": "2026-05-11"}

The value for a consumer is separating "this redirect is two years old, I've surely seen it" from "this appeared last week, my index is stale". Dates span 2024-02-19 to 2026-08-05.

The other 491 entries are vanity or legacy paths that were never a page's location, so there's no date to give. The docs say that explicitly, because an absent date would otherwise read as an undated move.

Generated, not committed — gitignored alongside data/examples.json and the other derived files here, so a snapshot of git history can't go stale. Every lookup tolerates the file being absent, so a bare hugo without make still builds and simply publishes no dates. Verified both ways.

Not built: deleted pages, and why

This was the other half of the item, and it can't be done honestly. git cannot reliably distinguish a deletion from a move it failed to detect.

Of 195 apparent deletions in this history, 83 have a same-named page somewhere else today:

Apparently deleted Almost certainly moved to
/develop/reference/cluster-spec operate/oss_and_stack/reference/cluster-spec.md
/develop/manual/keyspace-notifications develop/pubsub/keyspace-notifications.md
/develop/manual/client-side-caching develop/clients/client-side-caching.md

Those fell below git's rename similarity threshold and were recorded as delete-plus-add. And the remaining 112 are no safer — a page can be renamed and relocated at once, which no name-matching heuristic can see.

Publishing that list would tell a consumer to discard a citation that still resolves, which is worse than telling them nothing. So instead the docs now state plainly that a URL missing from the map is not necessarily gone, and that we deliberately don't claim deletions.

A list that's 40% wrong under the first check anyone would run against it isn't worth shipping.

Verification

Full build with a production-style baseURL:

Result
redirects / ambiguous / shadowed 1,061 / 27 / 10
carrying moved_on 570
tombstone coverage 1,061 from 1,061
build warnings 10 — the same pre-existing REF_NOT_FOUND set already on main (DOC-6955)
with data/page-moves.json absent builds clean, no dates

🤖 Generated with Claude Code


Note

Low Risk
Docs build pipeline and published redirect metadata only; optional field when data file is missing, no auth or runtime app changes.

Overview
Adds moved_on to entries in the published redirects.json map when git history can date a real page move, so consumers can tell stale vs recently changed redirects from vanity aliases with no date.

A new build/generate_page_moves.py step (wired into make hugo / serve_hugo via page_moves) scans rename records and writes gitignored data/page-moves.json; layouts/index.redirects.json merges those dates into unambiguous redirect entries. CI’s latest build job now checks out with fetch-depth: 0 so shallow clones don’t silently ship zero dates; the script warns on shallow clones. AI agent docs clarify that missing moved_on means legacy alias, not undated move, and that deleted pages are intentionally not listed because git can’t reliably separate deletion from undetected moves.

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

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

DOC-6951

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

🧠 Redis Memory

Found 6 related items from repository history:

Memory updated at dd114ce

@github-actions

Copy link
Copy Markdown
Contributor

Comment thread build/generate_page_moves.py Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0d15581. Configure here.

Comment thread build/generate_page_moves.py Outdated
Base automatically changed from DOC-6951-publish-redirects to main August 10, 2026 13:41
andy-stark-redis and others added 3 commits August 10, 2026 14:42
…re not published

Item D3, half of it built and half of it deliberately not.

The built half is the dates. The map is rendered from .Aliases, which is everything
Hugo knows, and Hugo cannot say when a page moved because frontmatter does not record
it. git does. build/generate_page_moves.py writes those dates into
data/page-moves.json before the build and the template attaches a `moved_on` to each
redirect it can date: 570 of 1,061 entries. The rest are vanity or legacy paths that
were never a page's location, so there is no date to give -- which the docs now say
explicitly, because an absent date could otherwise read as an undated move.

Generated rather than committed, gitignored alongside data/examples.json and the other
derived files here, so a snapshot of git history cannot go stale. Every lookup
tolerates the file being missing, so a bare `hugo` without `make` still builds and
simply publishes no dates; verified both ways.

The half not built is deleted pages, and the measurement is the reason. git cannot
reliably tell a deletion from a move it failed to detect. Of 195 apparent deletions in
this history, 83 have a same-named page elsewhere today -- /develop/reference/cluster-spec
against operate/oss_and_stack/reference/cluster-spec.md, and so on -- so they almost
certainly moved by a delete-plus-add below the rename similarity threshold. The other
112 are no safer, because a page can be renamed and relocated at once and no
name-matching heuristic sees that.

Publishing those as deleted would tell a consumer to discard a citation that still
resolves, which is worse than telling them nothing. So the docs now state that a URL
missing from the map is not necessarily gone, and that we deliberately do not claim
deletions. That is a better answer than a list that is 40% wrong under the first check
anyone would run against it.

Verified on a full build with a production baseURL: 1,061 redirects, 27 ambiguous, 10
shadowed, 570 dated, tombstone coverage still 1,061 from 1,061, and the same ten
pre-existing REF_NOT_FOUND warnings that main already emits.

Learned: the deletions half looked like the easy other half of this item and was the part that could not be done honestly -- 83 of 195 apparent deletions had a live successor, so shipping the list would have instructed consumers to drop working citations
Constraint: the map never claims a page was deleted, because git cannot distinguish a deletion from an undetected move, and a wrong deletion is worse for a consumer than no entry at all
Constraint: data/page-moves.json is generated before each build and gitignored, so the dates cannot drift from the history they come from
Ticket: DOC-6951
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…th no dates

The dates would have been empty in production. generate_page_moves.py reads git rename
records, and the only job that runs `make ci` checks out with actions/checkout's default
fetch-depth of 1. A shallow clone has no rename records, so the scan finds nothing,
reports zero moves and exits perfectly happily -- the map would have shipped without a
single date while every local build showed hundreds.

Verified by running the generator inside a --depth 1 clone: 0 dates, no error.

Two changes. The latest build now checks out with fetch-depth: 0, and only that job,
since the versioned matrix builds run bare `hugo` and never generate the map. And the
generator now detects a shallow clone, warns as an Actions annotation, and records
`shallow_clone` in the output so nobody has to guess whether a file with no dates means
a corpus without moves or a clone that could not see them.

This is the third time in this ticket that correct code was defeated by a default in
its surroundings: checkout depth in the alias workflow, the baseURL prefix in the map
template, and the Actions shell's errexit. All three were invisible locally and none
would have failed a test. The pattern is worth more than any of the individual fixes:
verify under the conditions CI creates, not the ones a laptop happens to have.

Learned: a shallow clone makes any git-history scan return an empty answer rather than an error, so the absence of data has to be distinguishable from an absence of history
Constraint: the latest build needs fetch-depth 0 because make ci reads git rename records; the versioned builds do not, and should not pay for it
Ticket: DOC-6951
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
classify() ran on every build and nothing read what it set. It decides whether each
move is already aliased, shadowed by a live page, contested or aimed at a draft, and
that costs a scan of every published URL plus the frontmatter of every file declaring an
alias. Dating needs only old_url and date.

Removing the call leaves the output byte-identical -- same 615 dates, same payload --
and takes the generator from 2.78s to 1.04s. Small in absolute terms, but it runs on
every build of the site, and work that nothing consumes is worth none of it.

It was there because this generator was written by importing the scanner's pipeline
wholesale, and find_moves plus classify is how every other caller uses it. Reusing a
pipeline is right; reusing the parts of it you do not need is just habit.

Learned: importing a pipeline wholesale carried a scan whose results were never read -- reuse the function, not the sequence of calls around it
Ticket: DOC-6951
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@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

Copy link
Copy Markdown
Contributor Author

Thanks @dwdougherty !

@andy-stark-redis
andy-stark-redis merged commit 93ac5ba into main Aug 11, 2026
90 checks passed
@andy-stark-redis
andy-stark-redis deleted the DOC-6951-page-moves branch August 11, 2026 13:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants