{"record":{"id":"0a7797feabfb2983","repo":"usebruno/bruno","slug":"invalid-collection-neither-bruno-json-nor-opencol","errorCode":null,"errorMessage":"Invalid collection: Neither bruno.json nor opencollection.yml found in the ZIP file","messagePattern":"Invalid collection: Neither bruno\\.json nor opencollection\\.yml found in the ZIP file","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/bruno-electron/src/ipc/collection.js","lineNumber":2723,"sourceCode":"\n        validateNoExternalSymlinks(tempDir, tempDir);\n\n        const extractedItems = fs.readdirSync(tempDir);\n        let collectionDir = tempDir;\n\n        if (extractedItems.length === 1) {\n          const singleItem = path.join(tempDir, extractedItems[0]);\n          const singleItemStat = fs.lstatSync(singleItem);\n          if (singleItemStat.isDirectory() && !singleItemStat.isSymbolicLink()) {\n            collectionDir = singleItem;\n          }\n        }\n\n        const brunoJsonPath = path.join(collectionDir, 'bruno.json');\n        const openCollectionYmlPath = path.join(collectionDir, 'opencollection.yml');\n\n        if (!fs.existsSync(brunoJsonPath) && !fs.existsSync(openCollectionYmlPath)) {\n          throw new Error('Invalid collection: Neither bruno.json nor opencollection.yml found in the ZIP file');\n        }\n\n        // Ensure config files are not symlinks\n        if (fs.existsSync(brunoJsonPath) && fs.lstatSync(brunoJsonPath).isSymbolicLink()) {\n          throw new Error('Security error: bruno.json cannot be a symbolic link');\n        }\n        if (fs.existsSync(openCollectionYmlPath) && fs.lstatSync(openCollectionYmlPath).isSymbolicLink()) {\n          throw new Error('Security error: opencollection.yml cannot be a symbolic link');\n        }\n\n        let collectionName = 'Imported Collection';\n        let brunoConfig = { name: collectionName, version: '1', type: 'collection', ignore: ['node_modules', '.git'] };\n        if (fs.existsSync(openCollectionYmlPath)) {\n          try {\n            const content = fs.readFileSync(openCollectionYmlPath, 'utf8');\n            const parsed = parseCollection(content, { format: 'yml' });\n            brunoConfig = parsed.brunoConfig || brunoConfig;\n            collectionName = brunoConfig.name || collectionName;","sourceCodeStart":2705,"sourceCodeEnd":2741,"githubUrl":"https://github.com/usebruno/bruno/blob/9bdd81c7bdc57006e5f5ebffb79321a8d979f712/packages/bruno-electron/src/ipc/collection.js#L2705-L2741","documentation":"Thrown during Bruno collection ZIP import. After extraction the importer picks the collection directory (the temp dir, or the single subdirectory when the archive wraps everything in one folder) and requires a `bruno.json` or `opencollection.yml` at its root. If neither marker file is present the ZIP is rejected as not a valid Bruno collection.","triggerScenarios":"Importing a ZIP whose `bruno.json`/`opencollection.yml` sits more than one directory below the archive root (the single-item unwrap only descends one level); a ZIP that drops request files at the root with no config; importing an archive exported from another tool (Postman/Insomnia) without conversion; the marker file was renamed or is itself inside a second nested folder.","commonSituations":"User re-zips a folder incorrectly (zips the parent so an extra wrapper appears, or nests the collection one level too deep); editing an exported archive and breaking its layout; importing a non-Bruno archive directly.","solutions":["Open the ZIP and confirm bruno.json (or opencollection.yml) is at the archive root or inside exactly one top-level folder.","Re-create the archive from inside the collection directory (e.g. `zip -r coll.zip .`) so the config sits at the right level.","If importing from another tool, run it through Bruno's import/conversion flow before zipping.","Verify the marker file name and extension exactly: `bruno.json` / `opencollection.yml`."],"exampleFix":"// before: archive layout\nmy-collection.zip\n  nested-folder/\n    my-collection/        <- bruno.json lives here (too deep)\n      bruno.json\n      get.bru\n// after: config at most one folder below root\nmy-collection.zip\n  my-collection/\n    bruno.json\n    get.bru","handlingStrategy":"validation","validationCode":"const isValidCollectionZip = (zipPath) => {\n  const AdmZip = require('adm-zip');\n  const zip = new AdmZip(zipPath);\n  const entries = zip.getEntries().map(e => e.entryName);\n  return entries.some(e => /^([^/]+\\/)?(bruno\\.json|opencollection\\.yml)$/.test(e));\n};","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always export collections via Bruno's own export rather than hand-zipping.","Before import, open the archive and confirm the marker config file's depth.","Keep collection directories flat - config at the collection root."],"tags":["collection","import","zip","validation"],"backgroundTag":null,"analyzedSha":"9bdd81c7bdc57006e5f5ebffb79321a8d979f712","analyzedAt":"2026-08-13T04:09:25.751Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}