argoproj/argo-workflows · error

failed to initialize semaphore %s: %w

Error message

failed to initialize semaphore %s: %w

What it means

newInternalSemaphore resolves the semaphore's limit via limitGetter.get before first use. If that fetch errors, the error is wrapped as 'failed to initialize semaphore' rather than silently defaulting the limit to 0 — a genuine failure stays distinguishable from an approval-gate semaphore that legitimately starts at limit 0.

Source

Thrown at workflow/sync/semaphore.go:47

		name:     name,
		lockType: lockTypeSemaphore,
	}
	sem := &prioritySemaphore{
		name:         name,
		limitGetter:  newCachedLimit(configMapGetter, syncLimitCacheTTL),
		pending:      &priorityQueue{itemByKey: make(map[string]*item)},
		semaphore:    sema.NewWeighted(int64(0)),
		lockHolder:   make(map[string]bool),
		nextWorkflow: nextWorkflow,
		logger:       logger.get,
	}
	// Resolve the limit directly through limitGetter rather than getLimit(), since
	// getLimit() falls back to the cache's zero-value on a fetch error, which would
	// make a genuine error indistinguishable from a semaphore that legitimately
	// starts at limit 0 (e.g. an "approval gate" held closed until raised).
	limit, changed, err := sem.limitGetter.get(ctx, name)
	if err != nil {
		return nil, fmt.Errorf("failed to initialize semaphore %s: %w", name, err)
	}
	if changed && !sem.resize(ctx, limit) {
		return nil, fmt.Errorf("failed to size semaphore %s to limit %d", name, limit)
	}
	return sem, nil
}

func (s *prioritySemaphore) getLimit(ctx context.Context) int {
	limit, changed, err := s.limitGetter.get(ctx, s.name)
	if err != nil {
		// Fall back to the last known limit (returned by the cache alongside
		// the error). Returning 0 here would make release() treat a transient
		// fetch failure as a downward resize and permanently leak a slot.
		s.logger(ctx).WithError(err).WithFields(logging.Fields{
			"name":          s.name,
			"fallbackLimit": limit,
		}).Error(ctx, "failed to get limit for semaphore, using last known limit")
		return limit

View on GitHub (pinned to 35bff19146)

Solutions

  1. Verify the semaphore ConfigMap exists in the workflow's namespace with the configured key, or the DB row exists
  2. Fix controller RBAC so it can read the ConfigMap (`argoproj.io` sync ConfigMaps)
  3. Check controller logs for the wrapped underlying error to identify the exact source
  4. Restart/reconcile the workflow after fixing so initialization is retried

Example fix

// before: missing ConfigMap
kubectl get configmap my-semaphore-cm -n argo   # NotFound
// after: create it
kubectl create configmap my-semaphore-cm -n argo --from-literal=limit=3
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure the semaphore ConfigMap exists and is readable before submitting
kubectl get configmap <name> -n <wf-namespace> \
  && kubectl auth can-i get configmap/<name> -n <wf-namespace> --as=system:serviceaccount:argo:argo-server

Try / catch

sem, err := manager.InitializeSemaphore(ctx, name)
if err != nil && strings.Contains(err.Error(), "failed to initialize semaphore") {
    return fmt.Errorf("check ConfigMap/DB for semaphore %q: %w", name, err)
}

Prevention

When it happens

Trigger: initializeSemaphore is called during controller startup or when a semaphore is first needed; limitGetter.get (ConfigMap read or DB query) returns an error — missing ConfigMap, RBAC denial, DB failure.

Common situations: Semaphore ConfigMap deleted or in a different namespace; controller service account lacking get rights on the ConfigMap; database-backed semaphore with unreachable DB; typo in the semaphore name/key.

Related errors


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