Skip to content

Add the /errors section and the /e/<id> error-message redirector - #521

Open
g-despot wants to merge 7 commits into
mainfrom
docs/error-message-redirector
Open

Add the /errors section and the /e/<id> error-message redirector#521
g-despot wants to merge 7 commits into
mainfrom
docs/error-message-redirector

Conversation

@g-despot

@g-despot g-despot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Error messages and log lines are gaining a stable id that resolves through https://docs.weaviate.io/e/<id> to a page explaining the error. This adds the redirect table and the /errors section it points into.

The section. Pages group errors by kind of problem, out of the site navigation but still indexable, since people google error text verbatim. Each entry gives the message verbatim, the cause, and the fix, so a reader who arrived from an error can act without following another link. Several ids share one subheading where they share one fix. No TOC; a side panel links to the new /improve-your-cluster page.

The redirects. 62 rules: 29 destinations, 32 frozen aliases for the bare ids already shipping in released weaviate-client wheels (those can never be renamed), and a trailing catch-all so an unrecognised id lands on the index rather than 404ing someone already having a bad day. The catch-all is a 302, not a 301, because its destination changes as each id gets a real page and a cached 301 would pin readers to the index permanently.

Ten ids point into /errors today. The rest keep their current destinations until their page exists; five resolve to the index by design, tracked with the content gaps.

_build_scripts/validate-redirects.js guards the invariants in CI, ahead of the build: catch-all last and 302, specific rules 301, sources lowercase and unique, and every /errors destination resolving to a real page and a real explicit anchor. It refuses to run its own checks if it cannot account for every declared rule, because a checker that silently under-parses reports success over input it never saw.

Merge before weaviate/weaviate#12738, which emits /e/core-mem001 and 404s until this is live.

Anchors were verified by rendering, not just existence: one destination passed existence, uniqueness and HTTP 200 while sitting inside a collapsed <details>, invisible to the reader. Worth knowing generally, since it applies well beyond this PR: the build warns on broken anchors but does not fail, only sees links rendered through Docusaurus <Link>, and never reads netlify.toml.

@orca-security-eu orca-security-eu 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.

Orca Security Scan Summary

Status Check Issues by priority
Passed Passed Infrastructure as Code high 0   medium 0   low 0   info 0 View in Orca
Passed Passed SAST high 0   medium 0   low 0   info 0 View in Orca
Passed Passed Secrets high 0   medium 0   low 0   info 0 View in Orca
Passed Passed Vulnerabilities high 0   medium 0   low 0   info 0 View in Orca

@g-despot

Copy link
Copy Markdown
Contributor Author

Converting to draft. The destinations are being redesigned: instead of pointing at existing docs pages, /e/<id> will resolve into a new dedicated /errors section, outside the docs navigation, whose pages group related errors under shared subheadings and carry a custom right-hand panel in place of a TOC. The redirect table and its ordering/alias machinery stand; the to targets do not.

Error and warning messages from Weaviate core and the clients now carry a
stable short link, https://docs.weaviate.io/e/<id>, instead of a deep docs
URL. The id is the contract; the destination is not. A message string ships
inside a released binary and can never be changed for users already running
it, so the id-to-page mapping lives here: a docs restructure becomes a
one-line redirect edit instead of six client releases, and it fixes readers
on old client versions retroactively.

Ids are <origin>-<category><nnn>, lowercase. The origin prefix is load-bearing:
the categories (auth, dep, con, bat, grpc, rbac) are universal, so core and
every client would each want a "deprecation" bucket and a flat namespace would
collide by default. It is not hypothetical -- the Python client already
overloads con004 and dep026 against two different messages each.

- 29 destination rules: 28 py-* and 1 core-mem001.
- 32 frozen aliases mapping the bare ids that ship in released
  weaviate-client wheels (Dep004: ...) onto their py-* form. These can never
  be renamed or removed and the list is closed.
- 5 of those aliases (auth002/003/004, dep028, grpc002) point at a py-* id
  with no destination yet, because the docs do not cover what those messages
  tell the user to do. They 404 today, exactly as the bare ids already do.
  Deliberately not pointed at approximately-related pages.

Every destination was verified against this commit's sources: URL resolved to
a file, anchors regenerated with the real @docusaurus/utils slugger (per-file
dedup state, {#explicit} overrides honored) and checked for uniqueness. The
one non-heading anchor, env-vars#GRPC_MAX_MESSAGE_SIZE, was verified against
src/components/APITable/index.jsx, which emits <tr id={firstCellText}>.

netlify.toml only. No client, core or docs content page was touched.

Refs: board 892e2d98 (parent), ac9a24a8 (client renumber + lowercased URLs),
7ece4ca8 (content gaps), 4090df96 (aggregate.md teaches a deprecated arg).
…he prefix rationale

Two review findings on fa6efe9b. Rule count is unchanged at 61 (29 destinations
+ 32 aliases); no rule was added or removed.

1. py-dep010 (and its alias dep010) pointed at
   client-libraries/python#library-imports. The anchor exists, is unique, and the
   page returns 200 -- and the link was still broken. That heading is nested in a
   collapsed <details> ("Migration guides - beta releases"). Docusaurus renders
   <details> React-controlled via theme-common Collapsible and does not act on
   the URL hash, so the browser's native auto-expand never fires and the reader
   lands on the page seeing nothing. It is also a historical v4.4b7 beta
   migration note, which is a poor destination for a warning still emitted today,
   so forcing the <details> open would not have been a fix either.

   Repointed to client-libraries/python/notes-best-practices#helper-classes:
   current guidance, and it teaches the exact fix the message asks for
   ("from weaviate.classes.config import Property" -- import from the submodule,
   not the weaviate root). Its prose and both code samples render outside the
   <details> on that section; only the submodule table is collapsed.

2. The header comment and the alias-table note both justified the origin prefix
   with the Python self-collision (con004/dep026). That is the wrong evidence.
   An origin prefix prevents CROSS-origin collision -- the categories are
   semantic, so core would reach for Dep0xx and Python already owns 19 of them --
   and that reason is sufficient alone. A prefix cannot fix a WITHIN-origin
   collision, since py-con004 is just as ambiguous as con004. The self-collision
   argues for a different rule: ids must be assigned from a registry, not
   harvested from prefixes a client already happens to use. Both points kept,
   the second no longer presented as proof of the first.

Anchor verification now has a third mandatory axis. Existence and uniqueness are
not sufficient; an anchor must also RENDER VISIBLE. All 29 destinations were
re-checked statically for collapsed-<details> ancestry (0 hidden) and the
affected pages confirmed in headless Chrome against the post-JS DOM, with
#library-imports as a positive control to prove the check detects the bug.

Refs: board 4bb13003 (the <details> anchor gotcha), 892e2d98 (parent),
ac9a24a8, 7ece4ca8, 4090df96.
…ve a page

A reader who follows an /e/<id> link arrives with one question, and the docs
page they landed on was written for someone browsing. /errors is written for the
arrival instead: one entry per message, carrying the message text verbatim, the
cause, and the fix, so the entry is a complete answer rather than a signpost.

Mechanics, in order of how much was already here:
- Out of the navigation: the pages are simply absent from sidebars.js, the same
  way /deploy/faqs/* and /weaviate/concepts/* already are. They stay in the
  sitemap and carry no noindex, because people search error text verbatim and
  that is a primary way they will arrive. Out of nav is about the sidebar, not
  about hiding.
- No table of contents: hide_table_of_contents, already used by ten index pages.
- Right-hand panel: src/theme/DocItem/Layout was ALREADY ejected, so this is a
  ~10 line change to it rather than a new swizzle. It is keyed on a `side_panel`
  frontmatter field that only these pages set, so a page without it renders
  through the unchanged path.

/improve-your-cluster ships with the panel that links to it, so the link is live
on day one.

Anchors are descriptive and explicit ({#...}), never the message id: several ids
share one anchor when they share one fix, which is the whole reason not to name
anchors after ids. py-dep017/023/024/025 share one; py-dep011/012/013 share one.

netlify.toml: only the `to` of the ten ids that now have an entry changed. The
32 frozen aliases, the lowercase rule and the rule ordering are untouched. One
stale sentence in the block comment is corrected: Docusaurus does check anchors
and warn, it just never reads this file.

_build_scripts/validate-links-pr.js seeds /errors and /improve-your-cluster.
linkinator only recurses under a seed's own path, so an out-of-nav section that
nothing links down into would never have been crawled at all.
…dead ends

RETRACTION FIRST. The message on abdab548 claims these pages are out of nav
"the same way /deploy/faqs/* and /weaviate/concepts/* already are". That is
wrong: both of those have autogenerated sidebars in sidebars.js and render one.
The pattern is precedented, just not by them. /support,
/deploy/installation-guides, /contributor-guide/getting-started and
/query-agent/guides/research_mode are among 37 built pages with no doc sidebar,
counted from the build output. The same message says hide_table_of_contents is
"already used by ten index pages"; the count is right, but weaviate/recipes.mdx
is not an index page, so read it as ten existing pages.

THREE THINGS WERE FACTUALLY WRONG.
- The bare-id claim was backwards. Bare ids like Dep024 are what ships TODAY in
  released packages; the prefixed form is the new one. The page told a reader
  looking at Dep024 in their terminal that they were on an old release.
- "For multimodal embeddings, the Google integration is not split by service"
  confused the module with the configuration. There is one multimodal module,
  but it still selects a service: multi2vec_google needs a project and a region,
  multi2vec_google_gemini needs neither. As written, a Gemini user migrating off
  multi2vec-palm was asked for a project id they do not have. Both forms now
  shown, both executed.
- Adoption was overstated. Only the Python client prints ids. Core does not
  print one as a prefix at all: it is gaining a docs_url log field that carries
  the id inside the URL, and that is not released yet. Said accurately now.

SELF-CONTAINEDNESS, which is the whole promise of the section.
- The second message shipping under py-dep026 (the `encoding` argument) was
  quoted nowhere, so a reader searching their literal string would have found
  nothing on the page their own redirect chose. Quoted verbatim in the
  multi-vector entry, and py-dep026 added to that entry's id line.
- The credential-header paragraph said the header was renamed without naming a
  single header. Now a table: X-Goog-Vertex-Api-Key and X-Goog-Studio-Api-Key
  are current, four older spellings are deprecated.
- The caution box named no error string. Now a table of all three, each
  reproduced live: KeyError 'vectorConfig' (legacy collection, current form),
  KeyError 'vectorIndexConfig' (named-vector collection, deprecated form: the
  deprecated argument is not merely deprecated there, it is broken), and
  WeaviateInvalidInputError for a name that does not exist.

The log excerpt on cluster-resources presented three lines from three different
code paths as one block, and two of the three carry the error in a separate
`error` field rather than in the message. Now a table saying which is which.

NAVIGATION. Out of every sidebar means no breadcrumbs, no paginator, no TOC,
and a desktop-only side panel that points forward rather than back. Every group
page now links to /errors in its intro, above the fold on mobile as well.

netlify.toml gains an /e/* catch-all, LAST in the file so first-match keeps
every specific rule winning. An unknown id used to 404; it now reaches the index
that explains what an id is. It also upgrades the five aliases whose canonical
id is deliberately unwritten (auth002, auth003, auth004, dep028, grpc002) from a
404 to a page. The checker now asserts the catch-all is last and that no other
splat exists in the block.

Also: the PaLM reconciliation moved into a note directly under the quoted
messages, four agentless passives made second person, and model-integrations now
says up front that these are Python client warnings and cross-links the vector
configuration entry, since its "Current" snippets silently carry that change too.

Verified: yarn build and yarn build-dev exit 0, no new broken links or anchors;
405/405 routes 200; 24 /e/ ids resolved through the table land on a visible
anchor or the index; all 17 snippet variants executed against Weaviate 1.38.4,
every deprecated form warns and every current form is silent.
…abricated log line

THE TENANT-ACTIVATION LOG LINE: the review finding was inverted, and the trap
that produced it is worth recording, because it has now caught three readers.

  v1.39.0      tag     .WithError(err).Errorf("loading shard %q failed", name)
  v1.38.10     tag     .WithError(err).Errorf("loading shard %q failed", name)
  origin/main          .WithError(err).Errorf("loading shard %q failed", name)
  stable/v1.39 branch  .Errorf("loading shard %q failed: %v", name, err)

The `: %v` form exists only on stable/v1.39. It arrived in c0a1f9bfa9 (2026-08-13),
`git tag --contains` on that commit is EMPTY, the branch is 692 commits past the
v1.39.0 tag, and the commit is not on main either. So it ships in no release
today. The bare checkout at ~/dev/weaviate sits on stable/v1.39, which is what
makes it read like main.

Second trap, same area: in zsh `git show $tag:path` silently prints NOTHING,
because `:path` is eaten as a history modifier. It exits 0, so it reads as "the
string is absent" rather than "the command did not run". Brace the ref.

Rather than pin the row to a version boundary that may move again, the row now
says the cause is either appended to that message or in a sibling `error` field
depending on the version, and tells the reader to search for the phrase rather
than the line. That is true on every version, released or not.

Also dropped: a JSON log entry I had synthesized to illustrate the field layout.
It was never captured from a running node, and on a page whose whole contract is
that the reader matches what is literally on their screen, an invented example is
the one thing that must never appear. The format and the fields are described
instead.

CATCH-ALL IS NOW 302, and it is the only rule in the block that is. The specific
rules stay 301 because an id means one thing forever and caching that is right.
The catch-all is the opposite: it fires for ids whose entry is not written yet,
so a 301 sitting in a browser cache would keep sending someone to the index long
after their id got a real entry, and no deploy could reach them.

THE INVARIANT IS NOW ACTUALLY ENFORCED. I had claimed the ordering was
machine-guarded when it lived in an uncommitted local script, which protects
nobody after this merges. _build_scripts/validate-redirects.js is committed,
wired into `yarn validate-redirects` and into the PR workflow ahead of the build,
and needs no build or network. It checks: the catch-all is last and 302, every
specific rule is 301, sources are lowercase and unique, and every /errors
destination resolves to a real page and a real {#anchor}. Each of those six
checks was confirmed to fail on a deliberately mutated copy before landing.

The Dep026 note pointed at the wrong name. The warning text says
`Configure.VectorIndex.MultiVectors`, plural, which does not exist; the working
accessor is `MultiVector`, singular. The plural is easy to reach for because
`Configure.MultiVectors` does exist at the top level, so the note now says which
is which instead of just "there is a typo".

Server floor added to the Google header table: the current headers need v1.27.7,
v1.26.12 or v1.25.27. A cluster still configured for PaLM is disproportionately
likely to predate them and would answer with an authentication error.

Snippet harness now prints the count it executed. I had reported 17 variants; it
is 16, and the number is no longer transcribed by hand.
…y under-parsing

The checker had the defect it existed to prevent. It matched one regex with a
fixed key order, fixed double quotes and three literal spaces in `to   =`, so a
rule appended after the catch-all became INVISIBLE to it the moment the
formatting drifted. Confirmed against the previous version, which exits 0 and
prints "1 catch-all (302, last)" while a dead rule sits after the catch-all:

  one space in `to = "`        OLD exit 0   NEW exit 1
  keys reordered               OLD exit 0   NEW exit 1
  single-quoted strings        OLD exit 0   NEW exit 1
  `force = true` interleaved   OLD exit 0   NEW exit 1
  a source the parser misses   OLD exit 0   NEW exit 1

(`force = true` appended after `status` was still caught by the old pattern,
since the first three keys still matched in order; interleaved is the case that
slips. Four of five, not four of four.)

Nothing is missed in the file as it stands today. It would have bitten on the
next hand-written edit, which is precisely when spacing drifts and precisely the
edit this check exists for.

TWO CHANGES, and the second is the one that matters.

Redirect tables are now parsed as tables: split on [[redirects]], read whatever
keys are present, any order, single or double quotes, extra keys ignored. That
closes the four known holes.

More importantly the parse is now asserted COMPLETE. The /e/ sources are counted
independently with the loosest pattern that can recognise one, and if that count
differs from the number of rules parsed the script exits 1 with PARSE GAP before
running a single check. Every other check reasons over the parsed set, so an
under-parse would let all of them pass over a set they never saw: success
reported over unexamined input, which is the same failure shape as a link
checker that validates zero files. Rule 1 closes the holes we found; rule 0
closes the ones we did not.

Comments are stripped before both parsing and counting, so prose in the block
that quotes these rules can neither be read as one nor counted as one.

Also added: a /e/ rule with no `to` or no `status` now fails rather than being
carried as a half-parsed object.

All twelve mutations were confirmed to exit 1, and netlify.toml was diffed
byte-identical afterwards.

NIT: the docs_url sample was written in logrus text format on a page that tells
the reader JSON is the default. It is now shown as a JSON key, with the text
form named as the LOG_FORMAT=text alternative. The default was re-verified at
v1.39.0 and v1.38.10: configure_api.go sets the JSON formatter unless
LOG_FORMAT=text.
@g-despot
g-despot force-pushed the docs/error-message-redirector branch from dbb0413 to 1b02ca6 Compare August 20, 2026 15:54
@g-despot g-despot changed the title Add the /e/<id> error-message redirector Add the /errors section and the /e/<id> error-message redirector Aug 20, 2026
@g-despot
g-despot marked this pull request as ready for review August 20, 2026 16:00
… cluster id

A /e/<id> redirect destination that carries its own query string makes Netlify
drop the query the request arrived with, rather than merging the two. So the
message id is set before the redirect rules run instead of being written into
the 29 destinations, and every destination stays query-free.

ClusterIdNotice renders on /improve-your-cluster and the four /errors pages
when a link carries a cluster id. Absent is the normal case, not an error: a
cluster has no identity until its raft leader commits one, and may never have
one at all. Absent, empty and malformed all render nothing. The check is a
format check with no lookup, so the page cannot be used to tell an id that
exists from one that does not, and the pattern is version agnostic because
core mints a v7 and falls back to v4.

The value is read after mount so the client's first render matches the
query-free server HTML. No canonical tag is added; Docusaurus already emits a
route-derived one with no query string.
@g-despot
g-despot force-pushed the docs/error-message-redirector branch from 5e693bc to 11fad73 Compare August 20, 2026 20:12
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.

1 participant