Fix taxonomy tools for taxonomies whose rest_base differs from slug#6
Merged
Conversation
Every taxonomy code path assumed a custom taxonomy's REST field name and
endpoint equal its slug. WordPress only guarantees that for built-ins; any
register_taxonomy() call can set a different rest_base (e.g. slug
documentation_category, rest_base documentation-categories), and for those
taxonomies the tools were broken three ways:
- assign_terms_to_content sent the slug as the post field name. WordPress
ignores unknown fields and returns 200, so the tool reported success
while writing nothing.
- get_content_terms read post fields by slug and returned {} even when
terms existed.
- getTaxonomyEndpoint() fell back to the slug, 404ing list/get/create/
update/delete_term for any taxonomy with a divergent rest_base.
Fixes, per the resolver pattern:
- New resolveTaxonomy(input, siteId) resolves slug OR rest_base through a
per-site cached /wp/v2/taxonomies lookup, hard-erroring on unknown
taxonomies (the silent slug fallback is what masked the no-op writes).
All term CRUD and field lookups route through it.
- assign_terms_to_content now writes to the rest_base field (collapsing
the category/post_tag special cases) and derives success from the
response: requested term IDs must appear in the updated content's
rest_base field, otherwise the tool errors. terms is now number[] —
string slugs were never accepted by the WordPress REST post fields.
- get_content_terms reads content[rest_base] in both the single-taxonomy
and all-taxonomies paths and fetches term details from the rest_base
endpoint.
- The local sync getContentEndpoint() copy (same slug-fallback bug for
post types) is gone; reuses the async site-aware resolver exported from
unified-content.ts.
- All 8 taxonomy tools accept site_id now, matching the rest of the
server (docs previously claimed this; the taxonomies cache is keyed per
site).
Found during a live run against a production site; see repro in the tool
descriptions and README notes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes taxonomy tooling to correctly handle custom taxonomies whose rest_base differs from their slug by resolving taxonomies via /wp/v2/taxonomies (per-site cached) and consistently using rest_base for endpoints and post fields. It also adds site_id support across taxonomy tools and improves documentation to reflect the new behavior and stronger error handling.
Changes:
- Added
resolveTaxonomy()to resolve slug/rest_base (per-site cached) and removed slug-based fallbacks that could silently no-op. - Updated all taxonomy term CRUD + content-term read/assign paths to use
rest_base, and addedsite_idto taxonomy tool schemas/handlers. - Updated README/CLAUDE docs to clarify slug vs
rest_base, error behavior, and write verification behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/tools/unified-taxonomies.ts | Adds per-site taxonomy resolution (slug/rest_base), routes all term + content-term operations through it, and adds site_id support. |
| src/tools/unified-content.ts | Exports getContentEndpoint() for reuse by taxonomy tools. |
| README.md | Documents slug vs rest_base handling and response-verified term assignment behavior. |
| CLAUDE.md | Mirrors documentation updates, including multi-site notes and resolver behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+496
to
+505
| // Fetch full term details for a list of term IDs from a taxonomy endpoint | ||
| const fetchTermDetails = (restBase: string, termIds: number[]) => Promise.all( | ||
| termIds.map(async (termId: number) => { | ||
| try { | ||
| return await makeWordPressRequest('GET', `${restBase}/${termId}`, undefined, { siteId: params.site_id }); | ||
| } catch { | ||
| return { id: termId, error: 'Could not fetch term details' }; | ||
| } | ||
| }) | ||
| ); |
Comment on lines
+96
to
99
| // Exported for reuse by unified-taxonomies.ts (assign_terms_to_content / get_content_terms) | ||
| export async function getContentEndpoint(contentType: string, siteId?: string): Promise<string> { | ||
| // Quick return for standard types | ||
| const standardMap: Record<string, string> = { |
Comment on lines
+435
to
+450
| const savedTerms: number[] | undefined = Array.isArray(response[restBase]) ? response[restBase] : undefined; | ||
| const missing = savedTerms === undefined | ||
| ? params.terms | ||
| : params.terms.filter(id => !savedTerms.includes(id)); | ||
|
|
||
| if (missing.length > 0) { | ||
| return { | ||
| toolResult: { | ||
| content: [{ | ||
| type: 'text', | ||
| text: `Error: assignment did not stick. Requested term(s) [${params.terms.join(', ')}] for taxonomy "${slug}" (field "${restBase}"), but the updated content reports ${savedTerms === undefined ? `no "${restBase}" field — the taxonomy may not be registered for content type "${params.content_type}" or not exposed in REST` : `[${savedTerms.join(', ')}]`}. Nothing was reported as saved for: [${missing.join(', ')}].` | ||
| }], | ||
| isError: true | ||
| } | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Every taxonomy code path assumed a custom taxonomy's REST field name and endpoint equal its slug. WordPress only guarantees that for built-ins — any
register_taxonomy()call can set a differentrest_base. Found live on wpfusion.com, wheredocumentation_categoryregistersrest_base: "documentation-categories"(EDD does the same:edd-categories,edd-tags):assign_terms_to_contentsilently assigned nothing — it sent the slug as the post field name; WordPress ignores unknown fields and returns 200, so the tool echoedsuccess: truewhile writing nothing. Worst failure mode available.get_content_termsreturned{}even when terms exist (field lookup by slug).getTaxonomyEndpoint()slug fallback).Fix
resolveTaxonomy(input, siteId): resolves slug or rest_base through a per-site cached/wp/v2/taxonomieslookup; hard-errors on unknown taxonomies (the silent fallback is what masked the no-op writes). All term CRUD and field lookups route through it.assign_terms_to_contentwrites to therest_basefield (collapses the category/post_tag special cases) and derives success from the response: requested IDs must appear in the updated content's field, elseisError.termsis nownumber[]— string slugs were never accepted by WP post fields.get_content_termsreadscontent[rest_base]in both paths.getContentEndpoint()copy (same fallback bug for post types); reuses the async site-aware resolver fromunified-content.ts.site_id(docs claimed this; it was never implemented).Verified live
list_terms/get_term/get_content_terms/assign_terms_to_contentby slugdocumentation_categoryall work and verify; rest_base input still accepted (backward compat); unknown taxonomy hard-errors listing available taxonomies.🤖 Generated with Claude Code