istio/istio · error

delegate HTTP route cannot contain delegate

Error message

delegate HTTP route cannot contain delegate

What it means

In Istio's delegated VirtualService routing, a route that is itself the target of a delegation (routeType == DelegateRoute) must not contain another 'delegate' stanza. Nested delegation would create ambiguous, possibly recursive route resolution, so one level of delegation is enforced.

Source

Thrown at pkg/config/validation/virtualservice.go:205

}

func validateHTTPRouteConflict(http *networking.HTTPRoute, routeType HTTPRouteType) (errs error) {
	if routeType == RootRoute {
		// This is to check root conflict
		// only delegate can be specified
		if http.Redirect != nil {
			errs = appendErrors(errs, fmt.Errorf("root HTTP route %s must not specify redirect", http.Name))
		}
		if http.Route != nil {
			errs = appendErrors(errs, fmt.Errorf("root HTTP route %s must not specify route", http.Name))
		}
		return errs
	}

	// This is to check delegate conflict
	if routeType == DelegateRoute {
		if http.Delegate != nil {
			errs = appendErrors(errs, errors.New("delegate HTTP route cannot contain delegate"))
		}
	}

	// check for conflicts
	if http.Redirect != nil {
		if len(http.Route) > 0 {
			errs = appendErrors(errs, errors.New("HTTP route cannot contain both route and redirect"))
		}

		if http.Fault != nil {
			errs = appendErrors(errs, errors.New("HTTP route cannot contain both fault and redirect"))
		}

		if http.Rewrite != nil {
			errs = appendErrors(errs, errors.New("HTTP route rule cannot contain both rewrite and redirect"))
		}

		if http.DirectResponse != nil {

View on GitHub (pinned to 8dc789c5cf)

Solutions

  1. Flatten to one delegation level: the root delegates directly to each leaf VirtualService
  2. Use match prefixes on the root to send different paths to different leaf VSs instead of chaining
  3. Move the second-level 'delegate' route's matching logic into the leaf VS's own route rules

Example fix

# before — child-vs (already delegated to) delegates again
http:
- match:
  - {uri: {prefix: /foo}}
  delegate: {name: leaf-vs, namespace: apps}

# after — child-vs routes directly
http:
- match:
  - {uri: {prefix: /foo/bar}}
  route:
  - destination: {host: foo-bar-svc}
Defensive patterns

Strategy: validation

Validate before calling

// a route reached via delegation must not delegate again
func delegateRoutesShallow(routes []*networking.HTTPRoute, isDelegateChild bool) bool {
	if !isDelegateChild {
		return true
	}
	for _, r := range routes {
		if r.GetDelegate() != nil {
			return false
		}
	}
	return true
}

Type guard

func isNestedDelegate(route *networking.HTTPRoute, parentWasDelegated bool) bool {
	return parentWasDelegated && route.GetDelegate() != nil
}

Prevention

When it happens

Trigger: A child VirtualService matched via a delegate root that itself declares 'delegate:' on one of its http routes — i.e., chaining root VS -> delegate VS -> another delegate VS.

Common situations: Teams trying to build hierarchies of delegated VirtualServices (root -> team -> app) and attempting to chain delegation a second level deep.

Related errors


AI-assisted analysis of istio/istio@8dc789c5cf (2026-08-15). Data as JSON: /api/errors/dcd83a3f25a6cb4c. Report an issue: GitHub.