argoproj/argo-workflows · error

failed to get token secret "%s": %w

Error message

failed to get token secret "%s": %w

What it means

After resolving the ServiceAccount, the interceptor fetches its token Secret (name derived via TokenNameForServiceAccount, typically `<sa-name>-token`). This error wraps the Kubernetes Get failure, so the webhook cannot be authorized with the SA's bearer token. Note the format argument passed is the secret variable's value at that point, which is the secret name string.

Source

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

	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. Check the secret exists: `kubectl get secret <sa-name>-token -n <namespace>`; if missing and on k8s >=1.24, create a long-lived token Secret (type kubernetes.io/service-account-token with kubernetes.io/service-account-name annotation) or a bound token.
  2. Confirm argo-server RBAC grants `get secrets` in the namespace.
  3. Recreate the ServiceAccount to regenerate a token secret on older clusters.
  4. If using projected tokens is intentional, this legacy webhook-auth path requires a static token secret — provision one manually.
  5. Read the wrapped error in server logs to separate NotFound from Forbidden.

Example fix

# k8s >=1.24: create a static token secret for the SA
apiVersion: v1
kind: Secret
metadata:
  name: my-sa-token
  namespace: <ns>
  annotations:
    kubernetes.io/service-account.name: my-sa
type: kubernetes.io/service-account-token
Defensive patterns

Strategy: validation

Validate before calling

sa, _ := clientset.CoreV1().ServiceAccounts(ns).Get(ctx, saName, metav1.GetOptions{})
tokName := saName + "-token"
if _, err := clientset.CoreV1().Secrets(ns).Get(ctx, tokName, metav1.GetOptions{}); err != nil {
	return fmt.Errorf("token secret %q missing for SA: %w", tokName, err)
}

Try / catch

secret, err := clientset.CoreV1().Secrets(ns).Get(ctx, secrets.TokenNameForServiceAccount(sa), metav1.GetOptions{})
if errors.IsNotFound(err) {
	// k8s >=1.24: create a static service-account-token Secret
} else if err != nil {
	// check RBAC get secrets
}

Prevention

When it happens

Trigger: The matched ServiceAccount exists but its token Secret was deleted (common after Kubernetes 1.24+, where tokens are no longer auto-created as Secrets and are projected instead), or RBAC blocks reading secrets, or the secret lives in a different namespace.

Common situations: Cluster upgraded from k8s <=1.23 to >=1.24: legacy `<sa>-token` secrets no longer auto-created; TokenController disabled; argo-server RBAC missing `get secrets`; secret manually cleaned up by a secret-sweeper job.

Related errors


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