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

  1. Pass a valid full semver string (MAJOR.MINOR.PATCH, optional prerelease/build), e.g. '1.2.3'.
  2. Normalize the source value first: strip leading 'v'/'=', trim whitespace, and coerce with semver.coerce(value) when the raw string may contain extra text.
  3. If no reliable version is known, omit airiVersion entirely — it is optional and skips the compatibility check.
  4. 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

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


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)