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

  1. Remove the FailOpen() call so the policy stays FailClosed as set by EmbeddedSkills
  2. Add an explicit FailClosed() after FailOpen() to restore a valid final state (last setter wins)
  3. 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

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


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/fd665cceeea61c1d. Report an issue: GitHub.