Skip to content

fix(k8s): show actionable error when kagent CRDs are not installed#447

Open
frivas-at-navteca wants to merge 2 commits intoagentregistry-dev:mainfrom
Navteca:fix/k8s-kagent-missing-crd-error
Open

fix(k8s): show actionable error when kagent CRDs are not installed#447
frivas-at-navteca wants to merge 2 commits intoagentregistry-dev:mainfrom
Navteca:fix/k8s-kagent-missing-crd-error

Conversation

@frivas-at-navteca
Copy link
Copy Markdown
Contributor

@frivas-at-navteca frivas-at-navteca commented Apr 20, 2026

Description

Motivation: When deploying agents to Kubernetes without kagent installed, users received a cryptic 500 Internal Server Error with a buried no matches for kind "Agent" in version "kagent.dev/v1alpha2" message that gave no actionable guidance.

What changed:

  • Added isKagentCRDNotFoundError(err error) bool in deployment_adapter_kubernetes.go that detects the missing CRD condition using two strategies:
    • Primary: errors.As(*k8smeta.NoKindMatchError) with exact Group == "kagent.dev" and Kind scoped to CRDs we actually deploy (Agent, McpServer) — handles the case where the REST mapper error propagates unwrapped
    • Fallback: string-based check for "no matches for kind" + "kagent.dev" — handles the case where controller-runtime wraps the error inside a *StatusError, breaking the typed chain
  • Detection happens in Deploy() on the adapter, not in the generic kubernetesApplyResource utility
  • Original error is preserved via %w for logging and chain inspection
  • 11 table-driven tests covering both detection paths, negative cases, partial installs, and nil

Fixes #318

Change Type

/kind fix

Changelog

fix(k8s): show actionable error message when deploying to Kubernetes without kagent installed

When deploying agents to Kubernetes without kagent installed, the user
received a raw 500 with a buried 'no matches for kind' message. This
change detects the missing CRD condition and returns a clear, actionable
error pointing to https://kagent.dev.

Detection uses two strategies:
- Primary: errors.As(*k8smeta.NoKindMatchError) with exact Group == "kagent.dev"
  and Kind scoped to the CRDs we actually deploy (Agent, McpServer)
- Fallback: string-based check to handle controller-runtime wrapping
  the REST mapper error inside a StatusError, which breaks the typed chain

Error is wrapped with %%w to preserve the chain for logging.

Closes agentregistry-dev#318
Copilot AI review requested due to automatic review settings April 20, 2026 17:46
@github-actions
Copy link
Copy Markdown

You already have 3 pull requests open. Please consider working on getting the existing ones merged before opening new ones. Thanks!

@github-actions
Copy link
Copy Markdown

You already have 3 pull requests open. Please consider working on getting the existing ones merged before opening new ones. Thanks!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 improves the Kubernetes deployment adapter’s error reporting when kagent CRDs are missing, so users get an actionable message instead of a cryptic “no matches for kind …” failure.

Changes:

  • Add isKagentCRDNotFoundError(err error) bool to detect missing kagent CRDs via typed (NoKindMatchError) and fallback string checks.
  • Update Deploy() to return an actionable install hint when missing-CRD is detected (while preserving the original error via wrapping).
  • Add table-driven tests covering typed and fallback detection scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
internal/registry/platforms/kubernetes/deployment_adapter_kubernetes.go Adds missing-CRD detection and returns a user-actionable error message on deploy.
internal/registry/platforms/kubernetes/deployment_adapter_kubernetes_platform_test.go Adds unit tests for the missing-CRD detection helper.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/registry/platforms/kubernetes/deployment_adapter_kubernetes.go Outdated
Comment on lines +177 to +183
// kagentKinds is the set of CRD Kinds we deploy via kagent. Checking Kind
// prevents false positives from other kagent.dev CRDs absent due to partial
// installs with a different remediation path.
kagentKinds := map[string]bool{
"Agent": true,
"McpServer": true,
}
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

kagentKinds is allocated on every call. Since this helper can be called multiple times during a request (and the set is static), consider making it a package-level map[string]struct{} (or []string + switch) to avoid repeated allocations.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Comment on lines +783 to +798
// --- Fallback string path (StatusError wrapping breaks typed chain) ---
{
name: "fallback/StatusError-style wrapping preserves message text",
err: fmt.Errorf(`no matches for kind "Agent" in version "kagent.dev/v1alpha2"`),
want: true,
},
{
name: "fallback/no matches for kind but unrelated group",
err: fmt.Errorf(`no matches for kind "Foo" in version "other.io/v1"`),
want: false,
},
{
name: "fallback/contains kagent.dev but not the kind-match phrase",
err: fmt.Errorf("connection refused to kagent.dev endpoint"),
want: false,
},
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

The fallback tests don’t include a case where the message contains kagent.dev + no matches for kind but the Kind is not one you deploy (e.g., UnknownKind in kagent.dev/v1alpha2). Adding that negative case would lock in the intended allowlist behavior and prevent regressions if the fallback matching is tightened.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Comment on lines +190 to +193
// Fallback: handle the case where controller-runtime wrapped the REST mapper
// error as a StatusError, breaking the typed chain.
msg := err.Error()
return strings.Contains(msg, "no matches for kind") && strings.Contains(msg, "kagent.dev")
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

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

The fallback string-matching path ignores the kagentKinds allowlist used in the typed NoKindMatchError path. That makes the behavior inconsistent with the function’s intent/comment (and could misclassify other kagent.dev kind-mismatch errors as “kagent not installed”). Consider extracting the Kind (and group) from the message and checking it against the same allowlist, or at least requiring the message to contain kind "Agent" or kind "McpServer" in addition to kagent.dev.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

…rnetes.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown

You already have 3 pull requests open. Please consider working on getting the existing ones merged before opening new ones. Thanks!

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.

show a better error message when deploying to k8s without kagent installed

2 participants