{"record":{"id":"fd665cceeea61c1d","repo":"larksuite/cli","slug":"embeddedskills-requires-failclosed-do-not-call","errorCode":null,"errorMessage":"EmbeddedSkills() requires FailClosed; do not call FailOpen() after EmbeddedSkills()","messagePattern":"EmbeddedSkills\\(\\) requires FailClosed; do not call FailOpen\\(\\) after EmbeddedSkills\\(\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"extension/platform/builder.go","lineNumber":203,"sourceCode":"\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}\n\n// MustBuild panics if Build() would return an error. Designed for\n// init():\n//","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/extension/platform/builder.go#L185-L221","documentation":"Build() rejects the plugin when a SkillsOverlay exists but the final failure policy is FailOpen. EmbeddedSkills() forces FailClosed (matching Restrict), and a later FailOpen() leaves an invalid final state that Build() rejects. A later FailClosed() restores validity.","triggerScenarios":"b.EmbeddedSkills(spec) followed by b.FailOpen(), then Build(). Checked at Build time against final state: b.skillsOverlay != nil && FailurePolicy == FailOpen.","commonSituations":"Builder helper that appends FailOpen globally; chained fluent calls reordered during refactoring; assuming FailOpen is a harmless default for resilience.","solutions":["Remove the FailOpen() call so the policy stays FailClosed as set by EmbeddedSkills","Add an explicit FailClosed() after FailOpen() to restore a valid final state (last setter wins)","If fail-open is required, ship the plugin without an embedded skills overlay"],"exampleFix":"// before\nb.EmbeddedSkills(spec).FailOpen() // invalid\n\n// after\nb.EmbeddedSkills(spec).FailClosed() // valid final state","handlingStrategy":"validation","validationCode":"if overlay != nil && policy == FailOpen {\n    return nil, fmt.Errorf(\"embedded skills require FailClosed\")\n}","typeGuard":null,"tryCatchPattern":"p, err := builder.Build()\nif err != nil {\n    // errors.Join of all builder errors; check for EmbeddedSkills/FailClosed conflict\n    return nil, fmt.Errorf(\"plugin build failed: %w\", err)\n}","preventionTips":["After EmbeddedSkills, either omit policy setters or end the chain with FailClosed()","Do not apply a global FailOpen default to plugins that embed skills","Use Build() instead of MustBuild while iterating to get the joined error list instead of a panic"],"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"}