argoproj/argo-workflows · error

AlreadyExists

AlreadyExists

Error message

sync limit cannot be created as it already exists

What it means

createSyncLimit refuses to create a sync limit when the target configmap already contains the requested key; existing limits must be updated instead. It fetches the configmap first and checks cm.Data[req.Key].

Solutions

  1. Call UpdateSyncLimit instead of CreateSyncLimit to change the existing key's value
  2. Delete the key from the configmap (or use DeleteSyncLimit) before re-creating
  3. Make provisioning idempotent: on AlreadyExists, fall back to update

Example fix

// before
_, err := client.CreateSyncLimit(ctx, req)
// after
_, err := client.CreateSyncLimit(ctx, req)
if st, ok := status.FromError(err); ok && st.Code() == codes.AlreadyExists {
    _, err = client.UpdateSyncLimit(ctx, &syncpkg.UpdateSyncLimitRequest{CmName: req.CmName, Namespace: req.Namespace, Key: req.Key, Limit: req.Limit})
}
Defensive patterns

Strategy: try-catch

Validate before calling

cm, err := kube.CoreV1().ConfigMaps(ns).Get(ctx, name, metav1.GetOptions{})
if err == nil {
    if _, exists := cm.Data[key]; exists { /* use update instead of create */ }
}

Try / catch

_, err := client.CreateSyncLimit(ctx, req)
if st, ok := status.FromError(err); ok && st.Code() == codes.AlreadyExists {
    _, err = client.UpdateSyncLimit(ctx, toUpdateReq(req))
}

Prevention

When it happens

Trigger: CreateSyncLimit targeting a configmap/key that already holds a limit value; note Get errors other than NotFound fall through to update semantics, but an existing key short-circuits with AlreadyExists.

Common situations: Re-running a bootstrap script that creates limits, two operators provisioning the same semaphore key, retrying a create after a partial success.

Related errors


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

Appendix: source

Thrown at server/sync/sync_cm.go:40

func (s *configMapSyncProvider) createSyncLimit(ctx context.Context, req *syncpkg.CreateSyncLimitRequest) (*syncpkg.SyncLimitResponse, error) {
	if req.Limit <= 0 {
		return nil, sutils.ToStatusError(fmt.Errorf("limit must be greater than zero"), codes.InvalidArgument)
	}

	if err := checkConfigMapPermission(ctx, "create", req.Namespace); err != nil {
		return nil, err
	}

	kubeClient := auth.GetKubeClient(ctx)

	configmapGetter := kubeClient.CoreV1().ConfigMaps(req.Namespace)

	cm, err := configmapGetter.Get(ctx, req.CmName, metav1.GetOptions{})
	if err == nil {
		_, has := cm.Data[req.Key]
		if has {
			return nil, sutils.ToStatusError(fmt.Errorf("sync limit cannot be created as it already exists"), codes.AlreadyExists)
		}
		return s.handleUpdateSyncLimit(ctx, &syncpkg.UpdateSyncLimitRequest{
			CmName:    req.CmName,
			Namespace: req.Namespace,
			Key:       req.Key,
			Limit:     req.Limit,
			Type:      syncpkg.SyncConfigType_CONFIGMAP,
		}, false)
	}

	cm = &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      req.CmName,
			Namespace: req.Namespace,
		},
		Data: map[string]string{
			req.Key: fmt.Sprint(req.Limit),
		},

View on GitHub (pinned to 35bff19146)