{"record":{"id":"985a3cca132d0154","repo":"larksuite/cli","slug":"restrict-nil-rule-must-not-be-nil","errorCode":null,"errorMessage":"Restrict(nil): rule must not be nil","messagePattern":"Restrict\\(nil\\): rule must not be nil","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"extension/platform/builder.go","lineNumber":138,"sourceCode":"\tif !b.validateHookName(hookName, \"on\") {\n\t\treturn b\n\t}\n\te, n, f := event, hookName, fn\n\tb.actions = append(b.actions, func(r Registrar) {\n\t\tr.On(e, n, f)\n\t})\n\treturn b\n}\n\n// Restrict contributes a pruning Rule. Calling Restrict implicitly\n// sets Restricts=true and FailurePolicy=FailClosed (the framework\n// requires both to coexist; the builder enforces the pairing so the\n// plugin author cannot accidentally ship a policy plugin under\n// FailOpen). It may be called more than once; each call adds one scoped\n// Rule and the engine OR-combines them.\nfunc (b *Builder) Restrict(rule *Rule) *Builder {\n\tif rule == nil {\n\t\tb.errs = append(b.errs, errors.New(\"Restrict(nil): rule must not be nil\"))\n\t\treturn b\n\t}\n\tb.caps.Restricts = true\n\tb.caps.FailurePolicy = FailClosed\n\t// Defensive clone: capture an independent snapshot so a caller that\n\t// reuses and mutates the same *Rule across multiple Restrict calls\n\t// gets distinct entries (mirrors the staging registrar's clone).\n\tcp := *rule\n\tcp.Allow = append([]string(nil), rule.Allow...)\n\tcp.Deny = append([]string(nil), rule.Deny...)\n\tcp.Identities = append([]Identity(nil), rule.Identities...)\n\tb.rules = append(b.rules, &cp)\n\treturn b\n}\n\n// EmbeddedSkills contributes a SkillsOverlay (see SkillsOverlay) customizing\n// the CLI's embedded skill content. It implies FailClosed: although skill\n// content is not a command-enforcement boundary, the overlay is a distribution","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/extension/platform/builder.go#L120-L156","documentation":"The Builder's Restrict() method was called with a nil *Rule. The builder collects errors into b.errs instead of panicking so chained construction can report all problems at Build() time. A nil rule cannot express any capability restriction, so the call is rejected.","triggerScenarios":"Calling builder.Restrict(nil), typically because a *Rule variable is nil (uninitialized pointer, a function that returned nil on failure, or a conditional that left the rule unset).","commonSituations":"Config parsing produced no rule but the code still calls Restrict(rule); refactoring moved rule construction and left a nil default; optional rule built only under a feature flag that is off.","solutions":["Construct a valid *Rule before calling Restrict, or skip the Restrict call entirely when no rule is needed","Check the rule-producing function's error/nil return before passing its result to Restrict","Call Build() and inspect the joined error; b.errs accumulates, so fix the nil input and rebuild"],"exampleFix":"// before\nvar rule *Rule\nb.Restrict(rule) // panics-free but records error\n\n// after\nif rule != nil {\n    b = b.Restrict(rule)\n}","handlingStrategy":"validation","validationCode":"if rule == nil {\n    return nil, fmt.Errorf(\"cannot build plugin: restrict rule is nil\")\n}\nb := builder.Restrict(rule)","typeGuard":"func hasRule(r *Rule) bool { return r != null() }","tryCatchPattern":null,"preventionTips":["Never pass a possibly-nil *Rule to Restrict; nil-check or skip the call","Let helper functions return (*Rule, error) and propagate errors instead of nil","Call Build()/MustBuild in tests to surface accumulated builder errors early"],"tags":["go","plugin-builder","nil-argument"],"backgroundTag":"nil-argument","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"}