multica-ai/multica · error
plugin source %q is not a directory or regular ZIP
Error message
plugin source %q is not a directory or regular ZIP
What it means
From loadPluginSource (cmd_plugin.go:149): the --source argument of `plugin validate`/`plugin install` stat'ed successfully but is neither a directory nor a regular file — i.e. a device, socket, pipe, or symlink-to-such. Only directories (packed on the fly) and regular ZIP files are accepted plugin sources.
Source
Thrown at server/cmd/multica/cmd_plugin.go:149
PluginKey string `json:"plugin_key"`
Version string `json:"version"`
ManifestDigest string `json:"manifest_digest"`
ArchiveDigest string `json:"archive_digest"`
ArtifactDigest string `json:"artifact_digest"`
SizeBytes int64 `json:"size_bytes"`
FileCount int `json:"file_count"`
}
func loadPluginSource(source string) ([]byte, plugincontract.Artifact, error) {
info, err := os.Stat(source)
if err != nil {
return nil, plugincontract.Artifact{}, err
}
if info.IsDir() {
return plugincontract.PackDirectory(source)
}
if !info.Mode().IsRegular() {
return nil, plugincontract.Artifact{}, fmt.Errorf("plugin source %q is not a directory or regular ZIP", source)
}
if info.Size() > plugincontract.MaxArchiveSize {
return nil, plugincontract.Artifact{}, fmt.Errorf("plugin archive exceeds %d bytes", plugincontract.MaxArchiveSize)
}
archive, err := os.ReadFile(source)
if err != nil {
return nil, plugincontract.Artifact{}, err
}
artifact, err := plugincontract.ValidateArtifact(archive)
return archive, artifact, err
}
func pluginDigestResult(artifact plugincontract.Artifact) pluginDigestOutput {
return pluginDigestOutput{
PluginKey: artifact.Manifest.Metadata.Key, Version: artifact.Manifest.Metadata.Version,
ManifestDigest: plugincontract.DigestBytes(artifact.CanonicalManifest), ArchiveDigest: artifact.ArchiveDigest,
ArtifactDigest: artifact.ArtifactDigest, SizeBytes: artifact.SizeBytes, FileCount: len(artifact.Files),
}View on GitHub (pinned to 2c0912b6ec)
Solutions
- Materialize the plugin as a real directory or a regular .zip file and pass that path.
- If using process substitution, write the zip to disk first: `zip -r /tmp/plugin.zip dir && multica plugin validate /tmp/plugin.zip`.
- Check what the path actually is: `ls -l <path>` — anything not starting with '-' or 'd' will be rejected.
Example fix
// before multica plugin validate <(make-plugin-zip) // FIFO -> not a directory or regular ZIP // after make-plugin-zip > /tmp/plugin.zip multica plugin validate /tmp/plugin.zip
Defensive patterns
Strategy: type-guard
Validate before calling
info, err := os.Stat(source)
if err != nil { return err }
if !info.IsDir() && !info.Mode().IsRegular() {
return fmt.Errorf("%s is mode %v; need directory or regular file", source, info.Mode())
} Type guard
func isPluginSourcePath(p string) bool {
info, err := os.Stat(p)
return err == nil && (info.IsDir() || info.Mode().IsRegular())
} Prevention
- Never pass process substitution or /dev fds as plugin sources.
- Verify with ls -l that the source is '-' (file) or 'd' (dir) before validate/install.
- Prefer directories over zips in CI to skip mode edge cases entirely.
When it happens
Trigger: Passing /dev/null, a named pipe, or a character device as the plugin source; a symlink whose target is a socket/pipe; on some setups a dangling mount point with a special mode.
Common situations: Shell completion or a typo picks up a special file; using process substitution `<(zip ...)` which creates a FIFO; copying a plugin archive with cp from a pipe.
Related errors
- plugin directory %q is not empty
- --%s-file %q: empty contents%s
- inspect plugin directory: %w
- plugin archive exceeds %d bytes
- validate Plugin: %w
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/efb7d4ce0849c706.
Report an issue: GitHub.