argoproj/argo-workflows · error

failed to get service account "%s": %w

Error message

failed to get service account "%s": %w

What it means

After a webhook request matches a client entry's secret parser, the interceptor looks up the named ServiceAccount in the ConfigMap's namespace via the Kubernetes API. This error is returned when that Get fails — most commonly the ServiceAccount does not exist (404), or the server lacks RBAC permission to read it. Without it the interceptor cannot fetch the token to impersonate the caller.

Source

Thrown at server/auth/webhook/interceptor.go:103

	if len(buf) > maxWebhookSize {
		return fmt.Errorf("webhook request body exceeds maximum size of 2MB")
	}
	defer func() { r.Body = io.NopCloser(bytes.NewBuffer(buf)) }()
	serviceAccountInterface := kube.CoreV1().ServiceAccounts(namespace)
	for serviceAccountName, data := range webhookClients.Data {
		r.Body = io.NopCloser(bytes.NewBuffer(buf))
		client := &webhookClient{}
		err := yaml.Unmarshal(data, client)
		if err != nil {
			return fmt.Errorf("failed to unmarshal webhook client \"%s\": %w", serviceAccountName, err)
		}
		i.logger.WithFields(logging.Fields{"serviceAccountName": serviceAccountName, "webhookType": client.Type}).Debug(r.Context(), "Attempting to match webhook request")
		ok := webhookParsers[client.Type](client.Secret, r)
		if ok {
			i.logger.WithField("serviceAccountName", serviceAccountName).Debug(r.Context(), "Matched webhook request")
			serviceAccount, err := serviceAccountInterface.Get(ctx, serviceAccountName, metav1.GetOptions{})
			if err != nil {
				return fmt.Errorf("failed to get service account \"%s\": %w", serviceAccountName, err)
			}
			tokenSecret, err := secretsInterface.Get(ctx, secrets.TokenNameForServiceAccount(serviceAccount), metav1.GetOptions{})
			if err != nil {
				return fmt.Errorf("failed to get token secret \"%s\": %w", tokenSecret, err)
			}
			r.Header["Authorization"] = []string{"Bearer " + string(tokenSecret.Data["token"])}
			return nil
		}
	}
	return nil
}

View on GitHub (pinned to 35bff19146)

Solutions

  1. Verify the ServiceAccount exists: `kubectl get sa <name> -n <namespace>` (namespace is the one in the webhook URL).
  2. Recreate it if missing: `kubectl create sa <name> -n <namespace>` and attach the expected RBAC role for workflow submission.
  3. Make the webhook-clients ConfigMap key exactly match the ServiceAccount name.
  4. Check argo-server RBAC allows `get serviceaccounts` and `get secrets` in the namespace (`kubectl auth can-i get serviceaccounts -n <ns> --as=system:serviceaccount:<ns>:argo-server`).
  5. Inspect the wrapped %w error in the argo-server logs to distinguish NotFound from Forbidden.
Defensive patterns

Strategy: validation

Validate before calling

if _, err := clientset.CoreV1().ServiceAccounts(ns).Get(ctx, saName, metav1.GetOptions{}); err != nil {
	return fmt.Errorf("webhook client SA %q missing: %w", saName, err)
}
ok, _ := kube.AuthCanI(clientset, "system:serviceaccount:argo:argo-server", "get", "serviceaccounts", ns)

Try / catch

sa, err := clientset.CoreV1().ServiceAccounts(ns).Get(ctx, saName, metav1.GetOptions{})
if errors.IsNotFound(err) {
	// recreate SA or fix ConfigMap key
} else if err != nil {
	// check RBAC / API server reachability
}

Prevention

When it happens

Trigger: Webhook request matched a webhook-clients entry whose key names a ServiceAccount that was deleted, renamed, exists in a different namespace than the event endpoint's namespace, or the argo-server service account lacks get on serviceaccounts in that namespace.

Common situations: Namespace recreated without the ServiceAccount; ConfigMap copied between namespaces keeping stale SA names; argo-server deployed with a ClusterRole missing `serviceaccounts` get; typo between the ConfigMap key and the actual SA name.

Related errors


AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03). Data as JSON: /api/errors/c021b9c3273555a4. Report an issue: GitHub.