argoproj/argo-workflows · error
invalid semaphore with both ConfigMapKeyRef and Database
Error message
invalid semaphore with both ConfigMapKeyRef and Database
What it means
getSemaphoreLockName validates a SemaphoreRef and rejects one that sets both ConfigMapKeyRef and Database — a semaphore must resolve to exactly one backend. This is a configuration validation error raised while computing the lock name for a workflow's synchronization entry.
Source
Thrown at workflow/sync/lock_name.go:55
func (ln *lockName) GetNamespace() string { return ln.namespace }
func (ln *lockName) GetResourceName() string { return ln.resourceName }
func (ln *lockName) GetKey() string { return ln.key }
func (ln *lockName) getKind() lockKind { return ln.kind }
func newLockName(namespace, resourceName, lockKey string, kind lockKind) *lockName {
return &lockName{
namespace: namespace,
resourceName: resourceName,
key: lockKey,
kind: kind,
}
}
func getSemaphoreLockName(sem *v1alpha1.SemaphoreRef, wfNamespace string) (*lockName, error) {
switch {
case sem.ConfigMapKeyRef != nil && sem.Database != nil:
return nil, fmt.Errorf("invalid semaphore with both ConfigMapKeyRef and Database")
case sem.ConfigMapKeyRef != nil:
namespace := sem.Namespace
if namespace == "" {
namespace = wfNamespace
}
return newLockName(namespace, sem.ConfigMapKeyRef.Name, sem.ConfigMapKeyRef.Key, lockKindConfigMap), nil
case sem.Database != nil:
namespace := sem.Namespace
if namespace == "" {
namespace = wfNamespace
}
return newLockName(namespace, sem.Database.Key, "", lockKindDatabase), nil
default:
return nil, fmt.Errorf("cannot get LockName for a Semaphore without a ConfigMapRef or Database")
}
}
func getMutexLockName(mtx *v1alpha1.Mutex, wfNamespace string) *lockName {View on GitHub (pinned to 35bff19146)
Solutions
- Remove the `configMapKeyRef` field if you intend database-backed synchronization
- Remove the `database` field if you intend ConfigMap-backed synchronization
- Lint the workflow before submit (`argo lint`) to catch the ambiguous spec early
Example fix
// before
synchronization:
semaphore:
configMapKeyRef:
name: my-cm
key: my-key
database:
key: my-lock
// after
synchronization:
semaphore:
database:
key: my-lock Defensive patterns
Strategy: validation
Validate before calling
func validateSemaphore(sem map[string]any) error {
_, hasCM := sem["configMapKeyRef"]
_, hasDB := sem["database"]
if hasCM && hasDB {
return fmt.Errorf("semaphore must set only one of configMapKeyRef or database")
}
if !hasCM && !hasDB {
return fmt.Errorf("semaphore must set configMapKeyRef or database")
}
return nil
} Type guard
func hasExactlyOneBackend(sem *SemaphoreRef) bool {
if sem == nil { return false }
return (sem.ConfigMapKeyRef != nil) != (sem.Database != nil)
} Try / catch
err := wf.Submit(ctx)
if err != nil && strings.Contains(err.Error(), "both ConfigMapKeyRef and Database") {
return fmt.Errorf("fix workflow spec: %w", err)
} Prevention
- Run `argo lint` on all workflows before submit
- When migrating between ConfigMap and DB semaphores, delete the old field in the same change
- Use schema-validated templates/CRDs rather than free-form YAML
When it happens
Trigger: A workflow/template specifies synchronization.semaphore with both `configMapKeyRef` and `database` fields populated; validation/lock-name resolution in the controller or lint hits the first switch case.
Common situations: Copy-paste editing a workflow that migrates from ConfigMap-based to database-based synchronization without removing the old field; templating/Helm values merging two sources of a semaphore spec.
Related errors
- cannot get LockName for a Semaphore without a ConfigMapRef o
- malformed workflow template parameter "%s": valueFrom is nil
- maxRetries cannot be less than 0
- baseDelay cannot be less than 0
- maxDelay cannot be less than 0
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/a9c96362b139aa59.
Report an issue: GitHub.