moeru-ai/airi · error · Error

Extension packages can contain only files and directories

Error message

Extension packages can contain only files and directories: ${relativePath}

What it means

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.

Solutions

  1. Delete special files (sockets/FIFOs) from the folder: `find . ! -type f ! -type d` to list them.
  2. Stop the process that created the socket/pipe (dev server) and clean up before importing.
  3. Copy only regular files into a clean directory and import that.
  4. Check for leftover artifacts in build output directories and exclude them.

Example fix

// before: folder contains ipc.sock (unix socket)
await staged('my-ext/') // -> throws
// after: clean special files
// find my-ext -not -type f -not -type d -delete
await staged('my-ext/')
Defensive patterns

Strategy: validation

Validate before calling

import { readdir } from 'node:fs/promises'
import { join } from 'node:path'
async function findSpecialFiles(dir: string): Promise<string[]> {
  const out: string[] = []
  for (const entry of await readdir(dir, { withFileTypes: true })) {
    const p = join(dir, entry.name)
    if (!entry.isFile() && !entry.isDirectory() && !entry.isSymbolicLink()) out.push(p)
    else if (entry.isDirectory()) out.push(...await findSpecialFiles(p))
  }
  return out
}

Try / catch

try {
  await staged(folder)
} catch (error) {
  if (error instanceof Error && error.message.includes('only files and directories')) {
    showUserError('Remove sockets/FIFOs/device files from the package folder.')
    return
  }
  throw error
}

Prevention

When it happens

Trigger: Calling inspectExtensionDirectory on a folder containing FIFOs, Unix sockets, device files, or other special filesystem objects anywhere in the tree.

Common situations: 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.

Related errors


AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17). Data as JSON: /api/errors/07614f636f04f9f1. Report an issue: GitHub.

Appendix: source

Thrown at apps/stage-tamagotchi/src/main/services/airi/plugins/host/directory-import.ts:191

    for await (const entry of entries) {
      entryCount += 1
      if (entryCount > extensionPackageLimits.entries) {
        throw new Error(`Extension package exceeds the ${extensionPackageLimits.entries} entry limit.`)
      }
      const path = join(directory, entry.name)
      const stats = await lstat(path)
      const relativePath = relative(sourceRealPath, path)

      if (stats.isSymbolicLink()) {
        throw new Error(`Extension packages cannot contain symbolic links: ${relativePath}`)
      }
      if (stats.isDirectory()) {
        directories.push(relativePath)
        pendingDirectories.push(path)
        continue
      }
      if (!stats.isFile()) {
        throw new Error(`Extension packages can contain only files and directories: ${relativePath}`)
      }
      totalBytes += stats.size
      if (totalBytes > extensionPackageLimits.totalBytes) {
        throw new Error('Extension package exceeds the 512 MiB size limit.')
      }
      files.push({ path, relativePath, size: stats.size })
    }
  }

  directories.sort()
  files.sort((left, right) => left.relativePath.localeCompare(right.relativePath))

  const manifestRelativePath = relative(sourceRealPath, manifestPath)
  const manifestFile = files.find(file => file.relativePath === manifestRelativePath)
  if (!manifestFile) {
    throw new Error(`Extension manifest does not exist: ${manifestPath}`)
  }
  if (manifestFile.size > extensionPackageLimits.manifestBytes) {

View on GitHub (pinned to 438a067dde)