moeru-ai/airi · error · Error
AIRI version must be a valid semantic version
Error message
AIRI version must be a valid semantic version: ${options.airiVersion} What it means
The ExtensionHost constructor validates options.airiVersion with semver.valid before storing it, because the version is later checked against extension manifests' engines.airi ranges via semver.satisfies. If airiVersion is provided but is not a valid semantic version string, the constructor throws at startup rather than failing later with confusing extension-compatibility errors.
Solutions
- Pass a valid full semver string (MAJOR.MINOR.PATCH, optional prerelease/build), e.g. '1.2.3'.
- Normalize the source value first: strip leading 'v'/'=', trim whitespace, and coerce with semver.coerce(value) when the raw string may contain extra text.
- If no reliable version is known, omit airiVersion entirely — it is optional and skips the compatibility check.
- Log/validate the value at the call site so a bad build/config value is caught before host construction.
Example fix
// before
new ExtensionHost({ airiVersion: remoteConfig.version })
// after
import semver from 'semver'
const coerced = semver.coerce(remoteConfig.version)
new ExtensionHost({ airiVersion: coerced ?? undefined }) Defensive patterns
Strategy: validation
Validate before calling
import semver from 'semver'
const v = options.airiVersion
if (v != null && !semver.valid(v)) throw new Error(`refusing to start: airiVersion '${v}' is not valid semver`) Type guard
function isValidAiriVersion(v) {
return v == null || (typeof v === 'string' && semver.valid(v) !== null)
} Try / catch
let host
try {
host = new ExtensionHost({ airiVersion })
} catch (err) {
if (String(err.message).startsWith('AIRI version must be a valid semantic version')) {
console.error(`Bad airiVersion '${airiVersion}'; falling back to no version pin.`, err)
host = new ExtensionHost()
} else throw err
} Prevention
- Always source airiVersion from a semver-conformant source (app.getVersion() of a properly tagged build), never free-form config.
- Coerce with semver.coerce() and trim the string before constructing the host.
- Omit airiVersion rather than passing a placeholder like 'dev' or 'unknown'.
- Validate the value in CI or at bootstrap so misconfigured builds fail early and visibly.
When it happens
Trigger: new ExtensionHost({ airiVersion: '...' }) is called where airiVersion is a truthy string that fails semver.valid — e.g. 'v1.2' (missing patch), '1.2.3.4', 'beta', an app.getPath/getVersion output in a non-semver format, or an env var string with whitespace.
Common situations: Wiring the host to app.getVersion() in an Electron build whose version was overridden; reading AIRI version from an env var or config file with a loose value; passing a semver range like '^1.0.0' instead of a concrete version; forgetting the leading 'v' is fine for semver.valid but extra suffixes are not.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Extension \` \` requires AIRI \` \`, but the running…
- Extension entrypoint id \
- Extension entrypoint is required for runtime \
- Extension \` \` does not support runtime \` \`.
- Extension \` \` does not support runtime \` \`.
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17).
Data as JSON: /api/errors/159f4c87d58308b6.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugin-sdk/src/plugin-host/core.ts:229
private readonly dependencies = new DependencyService()
private readonly kits = new KitRegistryService()
private readonly kitApis = new Map<string, KitRef<unknown>>()
private readonly kitApiWatchers = new Map<string, Set<() => Promise<void>>>()
private readonly modules = new KitApiBindingRegistryService()
private readonly extensionModuleResources = new Map<string, ExtensionModuleResourceTracker>()
private readonly permissions = new PermissionService()
private readonly airiVersion?: string
private readonly permissionResolver?: ExtensionHostOptions['permissionResolver']
private readonly persistedPermissionGrants = new Map<string, ModulePermissionGrant>()
private readonly resources = new ResourceService()
private readonly installContext: ExtensionHostInstallContext
constructor(options: ExtensionHostOptions = {}) {
this.loader = new FileSystemLoader()
this.runtime = options.runtime ?? 'electron'
if (options.airiVersion && !semver.valid(options.airiVersion)) {
throw new Error(`AIRI version must be a valid semantic version: ${options.airiVersion}`)
}
this.airiVersion = options.airiVersion
this.permissionResolver = options.permissionResolver
this.resources.setValue(protocolListProvidersEventName, [] as Array<{ name: string }>)
this.markCapabilityReady(protocolListProvidersEventName, { source: 'plugin-host' })
this.installContext = this.createInstallContext()
for (const contribution of options.contributions ?? []) {
this.installContribution(contribution)
}
}
private assertManifestCompatibility(manifest: ExtensionManifestV2, runtime: PluginRuntime) {
if (!manifest.engines.runtimes.includes(runtime)) {
throw new Error(`Extension \`${manifest.id}\` does not support runtime \`${runtime}\`.`)
}
if (this.airiVersion && !semver.satisfies(this.airiVersion, manifest.engines.airi, { includePrerelease: true })) {
throw new Error(View on GitHub (pinned to 438a067dde)