{"record":{"id":"b6b56c8e4313c7a0","repo":"argoproj/argo-workflows","slug":"failed-to-size-semaphore-s-to-limit-d","errorCode":null,"errorMessage":"failed to size semaphore %s to limit %d","messagePattern":"failed to size semaphore (.+?) to limit (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"workflow/sync/semaphore.go","lineNumber":50,"sourceCode":"\tsem := &prioritySemaphore{\n\t\tname:         name,\n\t\tlimitGetter:  newCachedLimit(configMapGetter, syncLimitCacheTTL),\n\t\tpending:      &priorityQueue{itemByKey: make(map[string]*item)},\n\t\tsemaphore:    sema.NewWeighted(int64(0)),\n\t\tlockHolder:   make(map[string]bool),\n\t\tnextWorkflow: nextWorkflow,\n\t\tlogger:       logger.get,\n\t}\n\t// Resolve the limit directly through limitGetter rather than getLimit(), since\n\t// getLimit() falls back to the cache's zero-value on a fetch error, which would\n\t// make a genuine error indistinguishable from a semaphore that legitimately\n\t// starts at limit 0 (e.g. an \"approval gate\" held closed until raised).\n\tlimit, changed, err := sem.limitGetter.get(ctx, name)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to initialize semaphore %s: %w\", name, err)\n\t}\n\tif changed && !sem.resize(ctx, limit) {\n\t\treturn nil, fmt.Errorf(\"failed to size semaphore %s to limit %d\", name, limit)\n\t}\n\treturn sem, nil\n}\n\nfunc (s *prioritySemaphore) getLimit(ctx context.Context) int {\n\tlimit, changed, err := s.limitGetter.get(ctx, s.name)\n\tif err != nil {\n\t\t// Fall back to the last known limit (returned by the cache alongside\n\t\t// the error). Returning 0 here would make release() treat a transient\n\t\t// fetch failure as a downward resize and permanently leak a slot.\n\t\ts.logger(ctx).WithError(err).WithFields(logging.Fields{\n\t\t\t\"name\":          s.name,\n\t\t\t\"fallbackLimit\": limit,\n\t\t}).Error(ctx, \"failed to get limit for semaphore, using last known limit\")\n\t\treturn limit\n\t}\n\tif changed {\n\t\ts.resize(ctx, limit)","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/workflow/sync/semaphore.go#L32-L68","documentation":"The limit was fetched successfully, but sem.resize(ctx, limit) returned false — the internal semaphore could not be resized to the newly discovered limit. newInternalSemaphore fails closed rather than run with a mis-sized semaphore.","triggerScenarios":"The resize target is invalid for the semaphore's current state (e.g. shrinking the limit below the number of current holders such that resize logic refuses), during initialization when the fetched limit differs from the cached default.","commonSituations":"Operator lowers a semaphore limit below the count of already-held slots (e.g. limit 2 -> 0 with 2 holders); concurrent config updates racing controller initialization; bad value in the ConfigMap (negative or non-numeric parsed oddly).","solutions":["Set the semaphore limit to a sane non-negative value that is compatible with current holders, or release holds before shrinking","Fix the ConfigMap/DB value and re-reconcile; restart the controller if the internal state is wedged","Increase the limit back above current holders if you intended a temporary zero (approval gate) only for new acquisitions"],"exampleFix":"// before: shrink below holders\nlimit: 0   # with 2 active holders -> resize refuses\n// after\ncurrentLimit: 2\nlimit: 3","handlingStrategy":"validation","validationCode":"// validate the limit before applying it\nfunc validLimit(v int, currentHolders int) error {\n    if v < 0 { return fmt.Errorf(\"limit must be >= 0\") }\n    if v < currentHolders { return fmt.Errorf(\"limit %d below %d active holders\", v, currentHolders) }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"sem, err := manager.InitializeSemaphore(ctx, name)\nif err != nil && strings.Contains(err.Error(), \"failed to size semaphore\") {\n    return fmt.Errorf(\"adjust semaphore limit vs current holders: %w\", err)\n}","preventionTips":["Never shrink a semaphore limit below current holders; release holds first","Validate ConfigMap values are non-negative integers","Coordinate limit changes with the operators holding locks"],"tags":["semaphore","resize","limit"],"backgroundTag":"semaphore-resize-failed","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}