moeru-ai/airi · error
Imported Extension entrypoints must be relative paths
Error message
Imported Extension entrypoints must be relative paths: ${entrypoint} What it means
When importing an unpacked Extension folder, AIRI inspects the manifest and validates every entrypoint listed in it. Entry points must be relative paths inside the package directory so they can be resolved and sandboxed against the imported folder's real path. This error is thrown by `inspectExtensionDirectory` when a manifest entrypoint is an absolute path (e.g. starts with `/` or a drive letter), which would bypass the package containment check.
Solutions
- Open the extension's manifest and rewrite each entrypoint as a path relative to the package root (e.g. `./index.js`, `src/main.js`).
- If a build tool generates the manifest, configure it to emit relative paths (or strip the package root prefix before writing entrypoints).
- Re-select the folder in AIRI after fixing the manifest so inspection re-runs.
Example fix
// before (manifest)
{ "entrypoints": { "main": "/Users/me/Downloads/my-ext/main.js" } }
// after
{ "entrypoints": { "main": "./main.js" } } Defensive patterns
Strategy: validation
Validate before calling
import { resolve, isAbsolute } from 'node:path'
function validateManifestEntrypoints(manifest, packageRoot) {
for (const [name, entrypoint] of Object.entries(manifest.entrypoints ?? {})) {
if (!entrypoint) continue
if (isAbsolute(entrypoint))
throw new Error(`entrypoint '${name}' must be relative: ${entrypoint}`)
if (!resolve(packageRoot, entrypoint).startsWith(packageRoot))
throw new Error(`entrypoint '${name}' escapes package root: ${entrypoint}`)
}
} Type guard
const isRelativeEntrypoint = (p) => typeof p === 'string' && p.length > 0 && !isAbsolute(p)
Try / catch
try {
await importExtension(folder)
} catch (err) {
if (String(err.message).includes('must be relative paths')) {
showHint('Fix manifest entrypoints to be relative to the package root, then re-select the folder.')
} else throw err
} Prevention
- Always write manifest entrypoints as paths relative to the package root.
- Lint manifests in CI to reject absolute entrypoint paths before packaging.
- Configure generators/builders to emit relative paths, stripping any root prefix.
When it happens
Trigger: Call `inspected`/`staged` on a directory whose manifest `entrypoints` values contain an absolute path (e.g. `"entrypoints": { "main": "/home/user/extension/main.js" }` or `"C:\\ext\\index.js"`). The manifest parses fine, but `isAbsolute(entrypoint)` is true for one of the values.
Common situations: Authors generating the manifest programmatically and accidentally writing an absolute filesystem path; hand-editing a manifest copied from another machine; build tooling resolving entrypoints to absolute paths before emitting the manifest; copying a manifest from a zip extraction log that recorded full paths.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- Extension entrypoint escapes the package folder
- Extension entrypoint id \
- Extension manifest does not exist
- Extension \` \` does not support runtime \` \`.
- Extension manifest is invalid
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17).
Data as JSON: /api/errors/03b06d266dba79d9.
Report an issue: GitHub.
Appendix: source
Thrown at apps/stage-tamagotchi/src/main/services/airi/plugins/host/directory-import.ts:239
}
catch (error) {
if (error instanceof SyntaxError) {
throw new Error(`Extension manifest is not valid JSON: ${error.message}`)
}
throw error
}
const parsedManifest = parseExtensionManifest(rawManifest)
if (!parsedManifest.success) {
throw new Error(`Extension manifest is invalid: ${formatManifestDiagnostics(parsedManifest.diagnostics)}`)
}
for (const entrypoint of Object.values(parsedManifest.manifest.entrypoints)) {
if (!entrypoint) {
continue
}
if (isAbsolute(entrypoint)) {
throw new Error(`Imported Extension entrypoints must be relative paths: ${entrypoint}`)
}
const resolvedEntrypoint = resolve(sourceRealPath, entrypoint)
if (!isContainedPath(sourceRealPath, resolvedEntrypoint)) {
throw new Error(`Extension entrypoint escapes the package folder: ${entrypoint}`)
}
await assertRegularFile(resolvedEntrypoint, 'Extension entrypoint')
const entrypointRealPath = await realpath(resolvedEntrypoint)
if (!isContainedPath(sourceRealPath, entrypointRealPath)) {
throw new Error(`Extension entrypoint resolves outside the package folder: ${entrypoint}`)
}
}
const fingerprint = createHash('sha256')
for (const directory of directories) {
fingerprint.update(`directory\0${directory}\0`)
}
for (const file of files) {
fingerprint.update(`file\0${file.relativePath}\0${file.size}\0`)View on GitHub (pinned to 438a067dde)