{"record":{"id":"f39c396f1577deb1","repo":"goharbor/harbor","slug":"empty-action","errorCode":null,"errorMessage":"empty action","messagePattern":"empty action","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/pkg/retention/policy/action/index/index.go","lineNumber":45,"sourceCode":"func init() {\n\t// Register retain action\n\tRegister(action.Retain, action.NewRetainAction)\n}\n\n// Register the performer with the corresponding action\nfunc Register(action string, factory action.PerformerFactory) {\n\tif len(action) == 0 || factory == nil {\n\t\t// do nothing\n\t\treturn\n\t}\n\n\tindex.Store(action, factory)\n}\n\n// Get performer with the provided action\nfunc Get(act string, params any, isDryRun bool) (action.Performer, error) {\n\tif len(act) == 0 {\n\t\treturn nil, errors.New(\"empty action\")\n\t}\n\n\tv, ok := index.Load(act)\n\tif !ok {\n\t\treturn nil, errors.Errorf(\"action %s is not registered\", act)\n\t}\n\n\tfactory, ok := v.(action.PerformerFactory)\n\tif !ok {\n\t\treturn nil, errors.Errorf(\"invalid action performer registered for action %s\", act)\n\t}\n\n\treturn factory(params, isDryRun), nil\n}\n","sourceCodeStart":27,"sourceCodeEnd":60,"githubUrl":"https://github.com/goharbor/harbor/blob/7b2fd08cc568955cca339afeefab27372840d936/src/pkg/retention/policy/action/index/index.go#L27-L60","documentation":"Returned by the retention action index Get when the action string is empty. Retention actions (e.g. retain) are registered at init time into a sync.Map keyed by action name; Get first rejects the empty key before lookup. It signals a malformed retention policy payload where the rule's action field is missing.","triggerScenarios":"Submitting a retention policy whose rule JSON omits the action field; programmatically building a policy model with an unset action and passing it to policy evaluation; API POST /retentions with an empty action in the rules array.","commonSituations":"Hand-crafted policy JSON from scripts or Terraform providers missing the action key; UI/API payload truncated during upgrade; copy-paste of policy templates between Harbor versions with schema changes.","solutions":["Set the action field on every rule in the retention policy payload (only 'retain' is a valid action in current Harbor)","Validate the policy JSON before submission, e.g. check rule.action is non-empty for each rule","If generating policies in code, assert on required fields before calling the retention API"],"exampleFix":"// before\nrule := map[string]any{\"template\": \"always\"}\n_, err := action.Get(\"\", params, false)\n\n// after\nrule := map[string]any{\"action\": \"retain\", \"template\": \"always\"}\n_, err := action.Get(\"retain\", params, false)","handlingStrategy":"validation","validationCode":"for _, r := range policy.Rules {\n    if len(r.Action) == 0 {\n        return fmt.Errorf(\"retention rule is missing an action\")\n    }\n}\n_, err := actionindex.Get(policy.Rules[0].Action, params, dryRun)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate policy payloads against the retention API schema before submitting","Use 'retain' as the action; it is the only registered action in current Harbor","Add integration tests that POST representative policies to catch schema drift"],"tags":["retention","policy","validation","action"],"backgroundTag":null,"analyzedSha":"7b2fd08cc568955cca339afeefab27372840d936","analyzedAt":"2026-08-16T00:00:10.961Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}