{"record":{"id":"15faa5ca12aa2621","repo":"larksuite/cli","slug":"parse-policy-yaml-rules-is-present-but-empty","errorCode":null,"errorMessage":"parse policy yaml: 'rules:' is present but empty; remove the key, or list at least one rule","messagePattern":"parse policy yaml: 'rules:' is present but empty; remove the key, or list at least one rule","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cmdpolicy/yaml/schema.go","lineNumber":121,"sourceCode":"\tdec.KnownFields(true)\n\tif err := dec.Decode(&s); err != nil {\n\t\treturn nil, fmt.Errorf(\"parse policy yaml: %w\", err)\n\t}\n\n\t// Reject multi-document input: yaml.v3 only decodes one document\n\t// per call, so a stray \"---\" followed by another document would\n\t// silently drop the trailing rule.\n\tvar extra fileSchema\n\tif err := dec.Decode(&extra); !errors.Is(err, io.EOF) {\n\t\tif err == nil {\n\t\t\treturn nil, fmt.Errorf(\"parse policy yaml: multiple YAML documents are not allowed\")\n\t\t}\n\t\treturn nil, fmt.Errorf(\"parse policy yaml: %w\", err)\n\t}\n\n\tif s.Rules != nil {\n\t\tif len(*s.Rules) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"parse policy yaml: 'rules:' is present but empty; remove the key, or list at least one rule\")\n\t\t}\n\t\tif !s.ruleSchema.isZero() {\n\t\t\treturn nil, fmt.Errorf(\"parse policy yaml: top-level rule fields cannot be combined with a 'rules:' list; move every rule under 'rules:'\")\n\t\t}\n\t\tout := make([]*platform.Rule, 0, len(*s.Rules))\n\t\tfor _, rs := range *s.Rules {\n\t\t\tout = append(out, rs.toRule())\n\t\t}\n\t\treturn out, nil\n\t}\n\n\t// Backward-compatible single top-level rule (flat fields).\n\treturn []*platform.Rule{s.ruleSchema.toRule()}, nil\n}\n","sourceCodeStart":103,"sourceCodeEnd":136,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/internal/cmdpolicy/yaml/schema.go#L103-L136","documentation":"Parse distinguishes 'rules:' absent (nil pointer) from 'rules:' present but empty (non-nil, len 0) via a pointer field. An empty rules list would yield a single all-zero Rule that lets every annotated command through — a fail-open foot-gun — so Parse rejects it with explicit guidance.","triggerScenarios":"Calling Parse on YAML containing 'rules:' with no entries, e.g. 'rules: []' or a bare 'rules:' key with nothing under it.","commonSituations":"Config generators rendering an empty list when no rules are configured; manually emptying the rules list while keeping the key; templates whose loop produced zero items.","solutions":["Add at least one rule entry under 'rules:'.","If no rules are intended, remove the 'rules:' key entirely (file then parses as a flat single rule).","Fix the generator/template to omit the key when the list is empty."],"exampleFix":"// before\nrules: []\n// after\nrules:\n  - name: default\n    allow: [\"docs/*\"]","handlingStrategy":"validation","validationCode":"if bytes.Contains(data, []byte(\"rules:\")) {\n\t// ensure at least one '- ' entry follows before handing to Parse\n}","typeGuard":null,"tryCatchPattern":"rules, err := yaml.Parse(data)\nif err != nil && strings.Contains(err.Error(), \"present but empty\") {\n\treturn fmt.Errorf(\"config generator produced an empty rules list; check upstream rule source: %w\", err)\n}","preventionTips":["Make generators omit 'rules:' when no rules exist.","Validate generated policy files in CI immediately after generation.","Never hand-empty a rules list; delete the key or replace with a real rule."],"tags":["go","yaml","config","empty-list"],"backgroundTag":"empty-config-list","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}