larksuite/cli · error

returned nil without an error

Error message

returned nil without an error

What it means

errorsNilResult ('returned nil without an error') is the sentinel for a plugin filesystem contract violation: the plugin's fs.FS/fs.File/fs.DirEntry/fs.FileInfo method returned a nil result with a nil error, which the fs interfaces forbid. The wrapper turns this into an fs.PathError so callers never receive a nil file/info that would panic later.

Source

Thrown at internal/skillpolicy/pluginfs.go:129

	}
	return safe, nil
}

func (p *pluginFS) recoverPath(op, path string, err *error) {
	if value := recover(); value != nil {
		*err = p.pathError(op, path, fmt.Errorf("panic: %v", value))
	}
}

func (p *pluginFS) pathError(op, path string, cause error) error {
	return &fs.PathError{
		Op:   op,
		Path: path,
		Err:  fmt.Errorf("plugin %q %s filesystem: %w", p.owner, p.field, cause),
	}
}

var errorsNilResult = fmt.Errorf("returned nil without an error")

func joinPath(parent, child string) string {
	if parent == "." {
		return child
	}
	return parent + "/" + child
}

type pluginFile struct {
	fsys   *pluginFS
	path   string
	source fs.File
}

func (f *pluginFile) Stat() (info fs.FileInfo, err error) {
	defer func() {
		if value := recover(); value != nil {
			info = nil

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Fix the plugin FS to return a real error (e.g. &fs.PathError{Op:..., Err: fs.ErrNotExist}) instead of (nil, nil).
  2. As a caller, read the fs.PathError — the plugin owner and op fields identify the offending method.
  3. Add contract tests to the plugin: every method must return non-nil results or a non-nil error.
  4. If using a mock/test FS in the plugin, implement it with fstest.MapFS to guarantee correct semantics.

Example fix

// before (plugin FS, contract violation)
func (f myFS) Open(name string) (fs.File, error) {
    file, ok := f.files[name]
    if !ok {
        return nil, nil // forbidden: nil, nil
    }
    return file, nil
}
// after
func (f myFS) Open(name string) (fs.File, error) {
    file, ok := f.files[name]
    if !ok {
        return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
    }
    return file, nil
}
Defensive patterns

Strategy: validation

Validate before calling

// before relying on a custom plugin FS, verify contract compliance
if err := fstest.TestFS(pluginFSInstance, "my-skill/SKILL.md"); err != nil {
    return fmt.Errorf("plugin FS violates fs.FS contract: %w", err)
}

Try / catch

if err != nil {
    if strings.Contains(err.Error(), "returned nil without an error") {
        return fmt.Errorf("plugin FS returned nil,nil — plugin bug: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: pluginFS.Open gets (nil, nil) from the plugin (pluginfs.go:45); Stat gets a nil FileInfo (pluginfs.go:66); ReadDir yields a nil DirEntry (pluginfs.go:98); pluginFile.Stat or pluginDirEntry.Info get nil FileInfo with nil error.

Common situations: A hand-rolled plugin FS returns nil,nil on a not-found path instead of fs.ErrNotExist; a generated or mock FS in a plugin forgets to return an error; a plugin FileInfo wrapper returns nil Info for its dir entries.

Related errors


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