{"record":{"id":"a0077bac1133969a","repo":"larksuite/cli","slug":"restrict-requires-failclosed-do-not-call-failop","errorCode":null,"errorMessage":"Restrict() requires FailClosed; do not call FailOpen() after Restrict()","messagePattern":"Restrict\\(\\) requires FailClosed; do not call FailOpen\\(\\) after Restrict\\(\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"extension/platform/builder.go","lineNumber":199,"sourceCode":"// are copied; Overlay/Base are fs.FS handles retained by reference (an fs.FS is\n// a read-only view, not caller-mutable state).\nfunc cloneSkillsOverlay(spec *SkillsOverlay) *SkillsOverlay {\n\tcp := *spec\n\tcp.Allow = append([]string(nil), spec.Allow...)\n\tcp.Remove = append([]string(nil), spec.Remove...)\n\tcp.ReferenceRemaps = append([]SkillRefRemap(nil), spec.ReferenceRemaps...)\n\treturn &cp\n}\n\n// Build returns the configured Plugin, or an error if any builder\n// step found a fault. MustBuild panics on the same error.\n//\n// FailOpen mismatches are checked against the final builder state, not in the\n// chained setters, because FailOpen/FailClosed and the contributing methods may\n// be called in either order.\nfunc (b *Builder) Build() (Plugin, error) {\n\tif len(b.rules) > 0 && b.caps.FailurePolicy == FailOpen {\n\t\tb.errs = append(b.errs, errors.New(\n\t\t\t\"Restrict() requires FailClosed; do not call FailOpen() after Restrict()\"))\n\t}\n\tif b.skillsOverlay != nil && b.caps.FailurePolicy == FailOpen {\n\t\tb.errs = append(b.errs, errors.New(\n\t\t\t\"EmbeddedSkills() requires FailClosed; do not call FailOpen() after EmbeddedSkills()\"))\n\t}\n\tif len(b.errs) > 0 {\n\t\treturn nil, errors.Join(b.errs...)\n\t}\n\treturn &builtPlugin{\n\t\tname:          b.name,\n\t\tversion:       b.version,\n\t\tcaps:          b.caps,\n\t\tactions:       b.actions,\n\t\trules:         b.rules,\n\t\tskillsOverlay: b.skillsOverlay,\n\t}, nil\n}","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/extension/platform/builder.go#L181-L217","documentation":"Build() rejects the plugin when Restrict() rules exist but the final failure policy is FailOpen. Restrict sets FailurePolicy to FailClosed at call time, so this error means FailOpen() was called after Restrict(). FailOpen/FailClosed may be called in either order, so the check runs against the final builder state in Build().","triggerScenarios":"b.Restrict(rule) followed by b.FailOpen(), then Build(). The check is len(b.rules) > 0 && FailurePolicy == FailOpen at Build time.","commonSituations":"Fluent-chain refactors that append FailOpen for 'resilience'; shared builder helpers that set FailOpen unconditionally; misunderstanding that setter order matters (it does — the final state is what counts).","solutions":["Remove the FailOpen() call — restriction policies require FailClosed","Move FailOpen() before Restrict() only if you also remove the restriction; otherwise it is still invalid","If fail-open behavior is genuinely needed, do not use Restrict; define the plugin without restrict rules"],"exampleFix":"// before\nb.Restrict(rule).FailOpen() // invalid\n\n// after\nb.Restrict(rule) // FailurePolicy stays FailClosed","handlingStrategy":"validation","validationCode":"if len(rules) > 0 && policy == FailOpen {\n    return nil, fmt.Errorf(\"restrict rules require FailClosed\")\n}","typeGuard":null,"tryCatchPattern":"p, err := builder.Build()\nif err != nil {\n    // errors.Join of all builder errors; check for Restrict/FailClosed conflict\n    return nil, fmt.Errorf(\"plugin build failed: %w\", err)\n}","preventionTips":["Never chain FailOpen() after Restrict(); the last policy setter wins, so keep FailClosed last when restricting","Do not add FailOpen in shared builder helpers for plugins that use restrictions","Rely on Build() (not MustBuild) during development to see all accumulated errors"],"tags":["go","plugin-builder","fail-open","policy-conflict"],"backgroundTag":"policy-configuration-conflict","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"}