Skip to content

Fix taxonomy tools for taxonomies whose rest_base differs from slug#6

Merged
jack-arturo merged 1 commit into
mainfrom
fix/taxonomy-rest-base
Jun 11, 2026
Merged

Fix taxonomy tools for taxonomies whose rest_base differs from slug#6
jack-arturo merged 1 commit into
mainfrom
fix/taxonomy-rest-base

Conversation

@jack-arturo

Copy link
Copy Markdown
Member

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 different rest_base. Found live on wpfusion.com, where documentation_category registers rest_base: "documentation-categories" (EDD does the same: edd-categories, edd-tags):

  1. assign_terms_to_content silently assigned nothing — it sent the slug as the post field name; WordPress ignores unknown fields and returns 200, so the tool echoed success: true while writing nothing. Worst failure mode available.
  2. get_content_terms returned {} even when terms exist (field lookup by slug).
  3. All term CRUD 404'd for divergent-rest_base taxonomies (getTaxonomyEndpoint() slug fallback).

Fix

  • New resolveTaxonomy(input, siteId): resolves slug or rest_base through a per-site cached /wp/v2/taxonomies lookup; 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_content writes to the rest_base field (collapses the category/post_tag special cases) and derives success from the response: requested IDs must appear in the updated content's field, else isError. terms is now number[] — string slugs were never accepted by WP post fields.
  • get_content_terms reads content[rest_base] in both paths.
  • Removed the local sync getContentEndpoint() copy (same fallback bug for post types); reuses the async site-aware resolver from unified-content.ts.
  • All 8 taxonomy tools now accept site_id (docs claimed this; it was never implemented).

Verified live

  • wpfusion.com (production): list_terms/get_term/get_content_terms/assign_terms_to_content by slug documentation_category all work and verify; rest_base input still accepted (backward compat); unknown taxonomy hard-errors listing available taxonomies.
  • drunk.support: full create→assign(category+tag, append)→read→delete cycle green.
  • Negative test: assigning a nonexistent term ID — WordPress returns 200 with the term silently dropped, and the new response-derived verification correctly reports the failure instead of fake success.

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings June 11, 2026 01:53
@jack-arturo jack-arturo merged commit 9f6c7f3 into main Jun 11, 2026
1 check passed
@jack-arturo jack-arturo deleted the fix/taxonomy-rest-base branch June 11, 2026 01:57

Copilot AI 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.

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 added site_id to 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
}
};
}
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