moeru-ai/airi · error · Error
must be a regular file
Error message
${label} must be a regular file: ${path} What it means
assertRegularFile rejects paths that are symlinks or not regular files, because manifests and package contents must be real files inside the imported directory. This hardens the extension importer against symlink-based escapes and special files (FIFOs, devices, directories).
Solutions
- Replace the symlink with a real copy of the file in the extension folder
- Ensure the labeled path is a regular file, not a directory or special file
- Avoid packaging extensions with symlinks; zip real files
Example fix
// before ln -s ~/shared/package.json extensions/myext/package.json // after cp ~/shared/package.json extensions/myext/package.json
Defensive patterns
Strategy: validation
Validate before calling
import { lstat } from 'node:fs/promises'
async function isRegularFile(p: string): Promise<boolean> {
try {
const s = await lstat(p)
return s.isFile() && !s.isSymbolicLink()
} catch { return false }
} Type guard
function isRegularStats(s: { isFile(): boolean; isSymbolicLink(): boolean }): boolean {
return s.isFile() && !s.isSymbolicLink()
} Try / catch
try {
await importFromDirectory(dir)
} catch (e) {
if (e instanceof Error && /must be a regular file:/.test(e.message)) {
console.warn(`${e.message} — replace symlinks with real copies before importing`)
return null
}
throw e
} Prevention
- Package extensions with real files, never symlinks
- lstat the manifest before import to catch symlink/directory issues early
- Watch for tools that replace package.json with a link to shared config
When it happens
Trigger: The manifest (or other labeled file) is a symlink, a directory, or a device/fifo at the inspected path.
Common situations: Repository checkouts where package.json is a symlink to a shared config; macOS alias-like links in downloaded archives; pointing at a directory instead of the manifest file.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- Extension packages cannot contain symbolic links
- Extension entrypoint resolves outside the package folder
- Extension packages can contain only files and directories
- Extension source must be a regular directory
- Extension source must be a regular directory
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17).
Data as JSON: /api/errors/45990906df7c9655.
Report an issue: GitHub.
Appendix: source
Thrown at apps/stage-tamagotchi/src/main/services/airi/plugins/host/directory-import.ts:116
optional: kit.optional ?? false,
})),
]
}
async function assertRegularFile(path: string, label: string): Promise<Stats> {
let stats: Stats
try {
stats = await lstat(path)
}
catch (error) {
if (isMissingPathError(error)) {
throw new Error(`${label} does not exist: ${path}`)
}
throw error
}
if (stats.isSymbolicLink() || !stats.isFile()) {
throw new Error(`${label} must be a regular file: ${path}`)
}
return stats
}
async function readManifestSnapshot(path: string, expectedSize: number): Promise<Buffer> {
const handle = await open(path, 'r')
try {
// One extra byte distinguishes the maximum valid manifest from a file
// that grew past the limit after the directory walk.
const buffer = Buffer.allocUnsafe(extensionPackageLimits.manifestBytes + 1)
let bytesRead = 0
while (bytesRead < buffer.byteLength) {
const result = await handle.read(buffer, bytesRead, buffer.byteLength - bytesRead, bytesRead)
if (result.bytesRead === 0) {
break
}
bytesRead += result.bytesRead
}View on GitHub (pinned to 438a067dde)