{"record":{"id":"07614f636f04f9f1","repo":"moeru-ai/airi","slug":"extension-packages-can-contain-only-files-and-directories","errorCode":null,"errorMessage":"Extension packages can contain only files and directories: ${relativePath}","messagePattern":"Extension packages can contain only files and directories: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/stage-tamagotchi/src/main/services/airi/plugins/host/directory-import.ts","lineNumber":191,"sourceCode":"    for await (const entry of entries) {\n      entryCount += 1\n      if (entryCount > extensionPackageLimits.entries) {\n        throw new Error(`Extension package exceeds the ${extensionPackageLimits.entries} entry limit.`)\n      }\n      const path = join(directory, entry.name)\n      const stats = await lstat(path)\n      const relativePath = relative(sourceRealPath, path)\n\n      if (stats.isSymbolicLink()) {\n        throw new Error(`Extension packages cannot contain symbolic links: ${relativePath}`)\n      }\n      if (stats.isDirectory()) {\n        directories.push(relativePath)\n        pendingDirectories.push(path)\n        continue\n      }\n      if (!stats.isFile()) {\n        throw new Error(`Extension packages can contain only files and directories: ${relativePath}`)\n      }\n      totalBytes += stats.size\n      if (totalBytes > extensionPackageLimits.totalBytes) {\n        throw new Error('Extension package exceeds the 512 MiB size limit.')\n      }\n      files.push({ path, relativePath, size: stats.size })\n    }\n  }\n\n  directories.sort()\n  files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))\n\n  const manifestRelativePath = relative(sourceRealPath, manifestPath)\n  const manifestFile = files.find(file => file.relativePath === manifestRelativePath)\n  if (!manifestFile) {\n    throw new Error(`Extension manifest does not exist: ${manifestPath}`)\n  }\n  if (manifestFile.size > extensionPackageLimits.manifestBytes) {","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/moeru-ai/airi/blob/438a067dde47aa0bdb46c2323d1fe293dc805218/apps/stage-tamagotchi/src/main/services/airi/plugins/host/directory-import.ts#L173-L209","documentation":"Entries that are neither regular files nor directories (checked after the symlink check) are rejected. Extension packages must contain only plain files and directories so the staged copy and size accounting behave deterministically.","triggerScenarios":"Calling inspectExtensionDirectory on a folder containing FIFOs, Unix sockets, device files, or other special filesystem objects anywhere in the tree.","commonSituations":"A running dev server left a .sock file inside the package folder; a test created a named pipe in the output directory; someone copied a /dev device node into the folder; tmpfs artifacts inside the source tree.","solutions":["Delete special files (sockets/FIFOs) from the folder: `find . ! -type f ! -type d` to list them.","Stop the process that created the socket/pipe (dev server) and clean up before importing.","Copy only regular files into a clean directory and import that.","Check for leftover artifacts in build output directories and exclude them."],"exampleFix":"// before: folder contains ipc.sock (unix socket)\nawait staged('my-ext/') // -> throws\n// after: clean special files\n// find my-ext -not -type f -not -type d -delete\nawait staged('my-ext/')","handlingStrategy":"validation","validationCode":"import { readdir } from 'node:fs/promises'\nimport { join } from 'node:path'\nasync function findSpecialFiles(dir: string): Promise<string[]> {\n  const out: string[] = []\n  for (const entry of await readdir(dir, { withFileTypes: true })) {\n    const p = join(dir, entry.name)\n    if (!entry.isFile() && !entry.isDirectory() && !entry.isSymbolicLink()) out.push(p)\n    else if (entry.isDirectory()) out.push(...await findSpecialFiles(p))\n  }\n  return out\n}","typeGuard":null,"tryCatchPattern":"try {\n  await staged(folder)\n} catch (error) {\n  if (error instanceof Error && error.message.includes('only files and directories')) {\n    showUserError('Remove sockets/FIFOs/device files from the package folder.')\n    return\n  }\n  throw error\n}","preventionTips":["Stop dev servers before importing; leftover .sock/.pid artifacts are rejected.","Clean build output directories of runtime artifacts before packaging.","Run `find <dir> ! -type f ! -type d` as a pre-import hygiene check."],"tags":["filesystem","validation","extensions","security"],"backgroundTag":"unsupported-source-type","analyzedSha":"438a067dde47aa0bdb46c2323d1fe293dc805218","analyzedAt":"2026-09-17T01:14:42.644Z","contentChangedAt":"2026-09-17T01:14:42.644Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}