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].

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)

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.