{"record":{"id":"e9417231830afc5c","repo":"matryer/xbar","slug":"no-plugin-files","errorCode":null,"errorMessage":"no plugin files","messagePattern":"no plugin files","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/plugins/install.go","lineNumber":108,"sourceCode":"\t)\n\tfor err == nil {\n\t\tcount++\n\t\tcandidateBaseName := fmt.Sprintf(\"%03d-%s\", count, plugin.Filename)\n\t\tcandidatePath = filepath.Join(i.PluginDir, candidateBaseName)\n\t\t_, err = os.Stat(candidatePath)\n\t}\n\tif !os.IsNotExist(err) {\n\t\treturn \"\", err\n\t}\n\treturn candidatePath, nil\n}\n\n// writePluginFiles iterates through the files defined for the plugin, and\n// writes them to the plugin installation directory, and sets the entry point\n// to be executable.\nfunc (i Installer) writePluginFiles(dstPath string, plugin metadata.Plugin) error {\n\tif len(plugin.Files) == 0 {\n\t\treturn errors.New(\"no plugin files\")\n\t}\n\tif len(plugin.Files) > 1 {\n\t\treturn errors.Errorf(\"only one plugin file supported: found %d.\", len(plugin.Files))\n\t}\n\tfor _, f := range plugin.Files {\n\t\tpluginFile := dstPath\n\t\tdir := path.Dir(pluginFile)\n\t\tif err := os.MkdirAll(dir, 0777); err != nil {\n\t\t\treturn errors.Wrapf(err, \"create directory %s for plugin\", dir)\n\t\t}\n\t\twriter, err := os.Create(pluginFile)\n\t\tif err != nil {\n\t\t\treturn errors.Wrapf(err, \"create plugin file %s\", f.Path)\n\t\t}\n\t\tdefer writer.Close()\n\t\treader := strings.NewReader(f.Content)\n\t\tif _, err := io.Copy(writer, reader); err != nil {\n\t\t\treturn errors.Wrapf(err, \"write plugin file %s\", f.Path)","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/matryer/xbar/blob/d624239058997c80118eaebe2e7f8331b3c765e0/pkg/plugins/install.go#L90-L126","documentation":"writePluginFiles copies the plugin's declared files into the installation directory. If the fetched metadata.Plugin has an empty Files slice there is nothing to install, so the installer aborts with errors.New(\"no plugin files\").","triggerScenarios":"Calling Install with a plugin whose metadata (from fetchPlugin or local metadata.Plugin struct) has Files == nil or len(Files) == 0, typically because the plugin.json descriptor omitted the files array.","commonSituations":"A plugin repository with a malformed or incomplete plugin.json; a registry/API response missing the files field due to server-side data issues; hand-constructed metadata.Plugin structs in tests.","solutions":["Add the plugin source file to the files array in the plugin.json metadata","Re-fetch the plugin metadata to get a complete descriptor","Verify the registry response includes the plugin.files field before calling Install"],"exampleFix":"// before (plugin.json)\n{ \"name\": \"myplugin\", \"files\": [] }\n// after\n{ \"name\": \"myplugin\", \"files\": [\"myplugin.5m.py\"] }","handlingStrategy":"validation","validationCode":"// before Install: ensure metadata declares at least one file\nif len(plugin.Files) == 0 {\n    return fmt.Errorf(\"plugin %q has no files; check its plugin.json\", plugin.Name)\n}","typeGuard":null,"tryCatchPattern":"// Go\nif err := installer.Install(ctx, pluginPath); err != nil {\n    if err.Error() == \"no plugin files\" {\n        return fmt.Errorf(\"plugin descriptor incomplete (missing files): %w\", err)\n    }\n    return err\n}","preventionTips":["Validate plugin.json (name + non-empty files array) before publishing","Verify registry responses include the files field","Test-install plugins from a staging registry"],"tags":["plugins","install","validation","metadata"],"backgroundTag":"missing-required-argument","analyzedSha":"d624239058997c80118eaebe2e7f8331b3c765e0","analyzedAt":"2026-09-02T22:38:22.007Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}