kubernetes/kops · error

namespace %q was provided for cluster-scoped object %v

Error message

namespace %q was provided for cluster-scoped object %v

What it means

The mirror image of the missing-namespace check: when the RESTMapper reports the GVK as cluster-scoped (root scope, e.g. ClusterRole, Namespace, CRD), passing a namespace is contradictory and the client refuses to build a namespaced dynamic client. This prevents silently applying a namespace filter that the API server would reject.

Source

Thrown at pkg/applylib/applyset/unstructuredclient.go:69

func (c *UnstructuredClient) dynamicResource(ctx context.Context, gvk schema.GroupVersionKind, ns string) (dynamic.ResourceInterface, error) {
	restMapping, err := c.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
	if err != nil {
		return nil, fmt.Errorf("error getting rest mapping for %v: %w", gvk, err)
	}
	gvr := restMapping.Resource

	switch restMapping.Scope.Name() {
	case meta.RESTScopeNameNamespace:
		if ns == "" {
			// TODO: Differentiate between server-fixable vs client-fixable errors?
			return nil, fmt.Errorf("namespace was not provided for namespace-scoped object %v", gvk)
		}
		return c.client.Resource(gvr).Namespace(ns), nil

	case meta.RESTScopeNameRoot:
		if ns != "" {
			// TODO: Differentiate between server-fixable vs client-fixable errors?
			return nil, fmt.Errorf("namespace %q was provided for cluster-scoped object %v", ns, gvk)
		}
		return c.client.Resource(gvr), nil

	default:
		// Internal error ... this is panic-level
		return nil, fmt.Errorf("unknown scope for gvk %s: %q", gvk, restMapping.Scope.Name())
	}
}

// Patch performs a Patch operation, used for server-side apply and client-side patch.
func (c *UnstructuredClient) Patch(ctx context.Context, gvk schema.GroupVersionKind, nn types.NamespacedName, patchType types.PatchType, data []byte, opt metav1.PatchOptions) (*unstructured.Unstructured, error) {
	dynamicResource, err := c.dynamicResource(ctx, gvk, nn.Namespace)
	if err != nil {
		return nil, err
	}

	name := nn.Name
	patched, err := dynamicResource.Patch(ctx, name, patchType, data, opt)

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Leave nn.Namespace empty ("") for cluster-scoped kinds
  2. Detect scope via the RESTMapper (or a known-kind list) before building NamespacedName and clear the namespace for root-scoped objects
  3. Audit defaulting code that blindly assigns a namespace to every object in an apply set

Example fix

// before
nn := types.NamespacedName{Name: "cluster-admin", Namespace: "default"}
// ClusterRole is cluster-scoped
client.Get(ctx, clusterRoleGVK, nn)
// after
nn := types.NamespacedName{Name: "cluster-admin"}
client.Get(ctx, clusterRoleGVK, nn)
Defensive patterns

Strategy: validation

Validate before calling

clusterScopedKinds := map[string]bool{"Namespace": true, "Node": true, "ClusterRole": true, "CustomResourceDefinition": true, "PersistentVolume": true}
if clusterScopedKinds[gvk.Kind] && nn.Namespace != "" {
	return fmt.Errorf("kind %s is cluster-scoped; clear nn.Namespace", gvk.Kind)
}

Type guard

func isClusterScopedCall(gvk schema.GroupVersionKind, nn types.NamespacedName) bool {
	return nn.Namespace != "" && clusterScopedKinds[gvk.Kind]
}

Try / catch

obj, err := client.Get(ctx, gvk, nn)
if err != nil {
	if strings.Contains(err.Error(), "was provided for cluster-scoped object") {
		return client.Get(ctx, gvk, types.NamespacedName{Name: nn.Name})
	}
	return err
}

Prevention

When it happens

Trigger: Calling Patch, Update, or Get with a cluster-scoped gvk while nn.Namespace is non-empty, e.g. getting a Namespace or ClusterRole with Namespace set to "default".

Common situations: Generic apply logic that always fills in a default namespace for every object; copying a namespace from a parent context onto all objects; YAML lists where cluster-scoped add-ons inherit a namespace from surrounding code.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/984d820190b2ccec. Report an issue: GitHub.