moeru-ai/airi · error
Extension copy does not match the reviewed package.
Error message
Extension copy does not match the reviewed package.
What it means
After copying the extension into a staging directory under <extensionsRoot>/.imports, the service re-inspects the staged copy and compares its manifest id and fingerprint to the inspected source. A mismatch means the copy operation produced files differing from the reviewed package (corrupted or partial copy). The service aborts before renaming the staging directory to its final destination.
Solutions
- Retry the import; transient mid-copy modification or I/O hiccups usually clear on a clean second attempt.
- Ensure the source folder is not being modified during the copy (pause sync tools, close editors/build watchers).
- Check free disk space and write permissions on extensionsRoot; clear leftover directories under <extensionsRoot>/.imports.
- If it persists, copy the extension folder manually to a local stable path and import from there.
Defensive patterns
Strategy: retry
Validate before calling
// pre-flight: ensure disk space and writability of extensionsRoot
await mkdir(extensionsRoot, { recursive: true })
await access(extensionsRoot, constants.W_OK) Try / catch
let lastError
for (let attempt = 0; attempt < 2; attempt++) {
try {
return await importAndCommit(planId)
} catch (error) {
lastError = error
if (!error.message.includes('does not match the reviewed package')) throw error
}
}
throw lastError Prevention
- Keep the source folder quiescent during import (no editors, watchers, or sync tools writing to it).
- Ensure adequate free disk space in the volume hosting extensionsRoot.
- Clear leftover staging directories under extensionsRoot/.imports after failures.
- Import from a local disk path rather than a network/synced volume when possible.
When it happens
Trigger: copyExtensionDirectory wrote incomplete or altered files (e.g. source files changed mid-copy, filesystem error during copy that copy did not propagate, symlink/case-sensitivity differences on the target volume) causing staged.fingerprint != inspected.fingerprint or a manifest id difference.
Common situations: Source folder modified while the copy was running; disk-full or permission errors silently degrading the copy; network/home-sync volumes with flaky behavior; copy across filesystems with different symlink/case handling; antivirus interference.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- Extension is already installed
- Extension package changed during copy. Select the folder…
- does not exist
- Extension directory importer is disposed.
- Extension import plan is missing or was already used.
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17).
Data as JSON: /api/errors/6b1779f3049640a6.
Report an issue: GitHub.
Appendix: source
Thrown at apps/stage-tamagotchi/src/main/services/airi/plugins/host/directory-import.ts:499
}
return await this.withSecurityScopedAccess(storedPlan.securityScopedBookmark, async () => {
const inspected = await inspectExtensionDirectory(storedPlan.sourcePath)
if (inspected.manifest.id !== storedPlan.preview.extensionId || inspected.fingerprint !== storedPlan.fingerprint) {
throw new Error('Extension source changed after review. Select the folder again.')
}
await this.assertDestinationAvailable(inspected.manifest.id)
await mkdir(this.extensionsRoot, { recursive: true })
const stagingRoot = join(this.extensionsRoot, '.imports')
await mkdir(stagingRoot, { recursive: true })
const stagingPath = join(stagingRoot, planId)
const destination = join(this.extensionsRoot, inspected.manifest.id)
try {
await copyExtensionDirectory(inspected.sourcePath, stagingPath)
const staged = await inspectExtensionDirectory(stagingPath)
if (staged.manifest.id !== inspected.manifest.id || staged.fingerprint !== inspected.fingerprint) {
throw new Error('Extension copy does not match the reviewed package.')
}
await this.assertDestinationAvailable(inspected.manifest.id)
await rename(stagingPath, destination)
this.plans.delete(planId)
return {
manifest: staged.manifest,
path: join(destination, extensionManifestFileName),
rootDir: destination,
}
}
catch (error) {
await rm(stagingPath, { recursive: true, force: true })
throw error
}
})
}
}
View on GitHub (pinned to 438a067dde)