{"record":{"id":"277d1f666e164f28","repo":"argoproj/argo-workflows","slug":"invalid-version","errorCode":null,"errorMessage":"invalid version","messagePattern":"invalid version","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"persist/sqldb/offload_node_status_repo.go","lineNumber":216,"sourceCode":"\t\t}\n\t\tx = make(map[string][]string)\n\t\tfor _, r := range records {\n\t\t\tx[r.UID] = append(x[r.UID], r.Version)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\nfunc (wdc *nodeOffloadRepo) Delete(ctx context.Context, uid, version string) error {\n\tif uid == \"\" {\n\t\treturn fmt.Errorf(\"invalid uid\")\n\t}\n\tif version == \"\" {\n\t\treturn fmt.Errorf(\"invalid version\")\n\t}\n\tlogCtx := wdc.log.WithFields(logging.Fields{\"uid\": uid, \"version\": version})\n\tlogCtx.Debug(ctx, \"Deleting offloaded nodes\")\n\treturn wdc.sessionProxy.With(ctx, func(s db.Session) error {\n\t\trs, err := s.SQL().\n\t\t\tDeleteFrom(wdc.tableName).\n\t\t\tWhere(db.Cond{\"clustername\": wdc.clusterName}).\n\t\t\tAnd(db.Cond{\"uid\": uid}).\n\t\t\tAnd(db.Cond{\"version\": version}).\n\t\t\tExec()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trowsAffected, err := rs.RowsAffected()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlogCtx.WithField(\"rowsAffected\", rowsAffected).Debug(ctx, \"Deleted offloaded nodes\")","sourceCodeStart":198,"sourceCodeEnd":234,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/persist/sqldb/offload_node_status_repo.go#L198-L234","documentation":"nodeOffloadRepo.Delete requires the version string of the offloaded node status to prevent deleting newer rows than the caller observed (optimistic-concurrency style deletion). An empty version means the caller has no snapshot to match, so the repo refuses to run the DELETE.","triggerScenarios":"Calling Delete(ctx, uid, \"\") — e.g. a node-status unoffload path where the version was never read from the workflow's Status.Conditions/OffloadNodeStatusVersion, or a zero-value struct was passed along.","commonSituations":"Deleting offloaded node status for a workflow that was never offloaded (so no version recorded); hydration code that forgot to read the offload node status version from the workflow status before deleting; upgrading from a version where version tracking did not exist.","solutions":["Read and pass the workflow's OffloadNodeStatusVersion (or the version recorded when offloading) into Delete","Skip deletion if the workflow was never offloaded (no version present)","Log and skip with a clearer error in the caller when version is empty"],"exampleFix":"// before\nerr := offloadRepo.Delete(ctx, uid, wf.Status.OffloadNodeStatusVersion)\n// after\nif wf.Status.OffloadNodeStatusVersion == \"\" {\n    log.Info(ctx, \"workflow %s not offloaded, skipping delete\", uid)\n    return nil\n}\nerr := offloadRepo.Delete(ctx, uid, wf.Status.OffloadNodeStatusVersion)","handlingStrategy":"validation","validationCode":"if version == \"\" {\n    return fmt.Errorf(\"cannot delete offloaded node status for %s: no offload version recorded (was it offloaded?)\", uid)\n}\nerr := offloadRepo.Delete(ctx, uid, version)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Track OffloadNodeStatusVersion alongside uid when offloading","Skip delete paths for workflows that were never offloaded","Assert version presence in tests covering unoffload logic"],"tags":["database","validation","persistence","concurrency"],"backgroundTag":"missing-required-identifier","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}