larksuite/cli · error
EmbeddedSkills() requires FailClosed; do not call FailOpen()
Error message
EmbeddedSkills() requires FailClosed; do not call FailOpen() after EmbeddedSkills()
What it means
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.
Source
Thrown at extension/platform/builder.go:203
cp.Allow = append([]string(nil), spec.Allow...)
cp.Remove = append([]string(nil), spec.Remove...)
cp.ReferenceRemaps = append([]SkillRefRemap(nil), spec.ReferenceRemaps...)
return &cp
}
// Build returns the configured Plugin, or an error if any builder
// step found a fault. MustBuild panics on the same error.
//
// FailOpen mismatches are checked against the final builder state, not in the
// chained setters, because FailOpen/FailClosed and the contributing methods may
// be called in either order.
func (b *Builder) Build() (Plugin, error) {
if len(b.rules) > 0 && b.caps.FailurePolicy == FailOpen {
b.errs = append(b.errs, errors.New(
"Restrict() requires FailClosed; do not call FailOpen() after Restrict()"))
}
if b.skillsOverlay != nil && b.caps.FailurePolicy == FailOpen {
b.errs = append(b.errs, errors.New(
"EmbeddedSkills() requires FailClosed; do not call FailOpen() after EmbeddedSkills()"))
}
if len(b.errs) > 0 {
return nil, errors.Join(b.errs...)
}
return &builtPlugin{
name: b.name,
version: b.version,
caps: b.caps,
actions: b.actions,
rules: b.rules,
skillsOverlay: b.skillsOverlay,
}, nil
}
// MustBuild panics if Build() would return an error. Designed for
// init():
//View on GitHub (pinned to 7fd6ef3c07)
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
Example fix
// before b.EmbeddedSkills(spec).FailOpen() // invalid // after b.EmbeddedSkills(spec).FailClosed() // valid final state
Defensive patterns
Strategy: validation
Validate before calling
if overlay != nil && policy == FailOpen {
return nil, fmt.Errorf("embedded skills require FailClosed")
} Try / catch
p, err := builder.Build()
if err != nil {
// errors.Join of all builder errors; check for EmbeddedSkills/FailClosed conflict
return nil, fmt.Errorf("plugin build failed: %w", err)
} Prevention
- 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
When it happens
Trigger: b.EmbeddedSkills(spec) followed by b.FailOpen(), then Build(). Checked at Build time against final state: b.skillsOverlay != nil && FailurePolicy == FailOpen.
Common situations: Builder helper that appends FailOpen globally; chained fluent calls reordered during refactoring; assuming FailOpen is a harmless default for resilience.
Related errors
- Restrict() requires FailClosed; do not call FailOpen() after
- Restrict(nil): rule must not be nil
- EmbeddedSkills(nil): spec must not be nil
- EmbeddedSkills() called more than once; a plugin owns at mos
- Invalid column: {column!r}
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/fd665cceeea61c1d.
Report an issue: GitHub.