larksuite/cli · error

ErrInvalidHostBase

ErrInvalidHostBase

Error message

%w: %w

What it means

ErrInvalidHostBase reports that the wrapper-provided base skill tree (the CLI's embedded skill content, when the plugin did not supply its own Base) is malformed — unreadable root, non-directory entries, invalid skill names, or missing/invalid SKILL.md. It is kept distinct from plugin Base errors so diagnostics point at the host integrator, not the plugin.

Source

Thrown at internal/skillpolicy/resolver.go:100

		refs, err := skillref.New(base, nil)
		if err != nil {
			return Resolution{}, err
		}
		return Resolution{Content: base, References: refs}, nil
	}

	owner, spec := specs[0].PluginName, specs[0].SkillsOverlay
	lower := base
	lowerLabel := "host Base"
	if spec.Base != nil {
		lower = protectPluginFS(owner, "Base", spec.Base)
		lowerLabel = "plugin Base"
	}
	upper := protectPluginFS(owner, "Overlay", spec.Overlay)
	lowerSnapshot, err := scanSkillTree(lowerLabel, lower)
	if err != nil {
		if spec.Base == nil {
			return Resolution{}, fmt.Errorf("%w: %w", ErrInvalidHostBase, err)
		}
		return Resolution{}, fmt.Errorf("plugin %q skill spec: %w", owner, err)
	}
	upperSnapshot, err := scanSkillTree("plugin Overlay", upper)
	if err != nil {
		return Resolution{}, fmt.Errorf("plugin %q skill spec: %w", owner, err)
	}
	if err := validateSelection(lowerSnapshot, spec); err != nil {
		return Resolution{}, fmt.Errorf("plugin %q skill spec: %w", owner, err)
	}
	var content fs.FS
	if lower == nil && upper == nil {
		content = nil
	} else {
		composed := newOverlayFS(lowerSnapshot, upperSnapshot, spec.Remove, spec.Allow)
		if err := validateRequiredSkills(composed); err != nil {
			return Resolution{}, fmt.Errorf("plugin %q skill spec: %w", owner, err)
		}

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Check whether cmd.SetEmbeddedSkillContent was called with a valid embedded skill FS in the wrapper main.
  2. Validate the base FS layout: every top-level entry must be a <skill>/ directory containing SKILL.md with valid metadata.
  3. Unwrap the cause (errors.Is/errors.As) for the specific scan error (root unreadable, invalid name, missing SKILL.md).
  4. If the plugin should own content, have it provide SkillsOverlay.Base so the host base is bypassed.

Example fix

// before (wrapper main)
func main() { cmd.Execute() } // never sets embedded skill content
// after
func main() {
    cmd.SetEmbeddedSkillContent(skills.Embedded)
    cmd.Execute()
}
Defensive patterns

Strategy: validation

Validate before calling

// verify the host base tree before calling the resolver
entries, err := fs.ReadDir(hostBase, ".")
if err != nil { return fmt.Errorf("host base unreadable: %w", err) }
for _, e := range entries {
    if !e.IsDir() { return fmt.Errorf("%s is not a skill dir", e.Name()) }
    if _, err := fs.Stat(hostBase, e.Name()+"/SKILL.md"); err != nil {
        return fmt.Errorf("skill %s missing SKILL.md: %w", e.Name(), err)
    }
}

Try / catch

if err != nil {
    if errors.Is(err, skillpolicy.ErrInvalidHostBase) {
        return fmt.Errorf("fix the wrapper's embedded skill content, not the plugin: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: ResolveWithReferences is called with a spec whose Base is nil, and scanSkillTree on the host base fs.FS fails: fs.ReadDir on '.' errors, a top-level entry is not a directory, a name fails isSkillName, a skill lacks SKILL.md, or the manifest is invalid.

Common situations: An external wrapper main forgot to call cmd.SetEmbeddedSkillContent (or embedded an empty/mis-shaped tree) while a plugin requested Allow/Remove; the embedded FS layout was renamed; a build step that generates skill content silently produced the wrong structure.

Related errors


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