moeru-ai/airi · error
Extension source changed after review. Select the folder…
Error message
Extension source changed after review. Select the folder again.
What it means
Before committing, commitPreparedPlan re-inspects the source directory and compares the manifest id and content fingerprint against what was captured during prepare/preview. If the folder contents changed between the user's review and the commit, it throws so the user never installs something different from what they saw. The fingerprint is effectively a checksum of the reviewed package.
Solutions
- Stop any process modifying the folder, then restart the import: select the folder again (prepare), review, and commit promptly.
- Verify manifest.json id still matches the previewed extension; revert edits made after preview.
- Exclude the folder from sync/build tools during import, or copy it to a stable location and import from there.
- Re-run prepare to regenerate the fingerprint, then commit without delay.
Defensive patterns
Strategy: validation
Validate before calling
// before commit, compare current folder fingerprint to the one from prepare
const current = await inspectExtensionDirectory(sourcePath)
if (current.fingerprint !== preparedFingerprint) {
throw new Error('Folder changed since review; re-select the folder.')
} Try / catch
try {
await importService.commit(planId)
} catch (error) {
if (error.message.includes('changed after review')) {
// prompt the user to re-select the folder and redo prepare -> commit
}
} Prevention
- Do not edit the extension folder (especially manifest.json) between preview and confirm.
- Pause file-sync or build tools that touch the folder during import.
- Commit promptly after reviewing the preview; long delays widen the race window.
When it happens
Trigger: Files in the selected extension folder added/removed/modified between prepare() and commit() such that inspectExtensionDirectory returns a different fingerprint, or the manifest.id changed (e.g. user swapped the folder contents or edited manifest.json after preview).
Common situations: User edits manifest.json while the confirm dialog is open; sync tools (iCloud/Dropbox/git checkout) modify the folder mid-review; a different folder is committed than the one previewed; build tooling rewrites the directory during the review window.
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 copy does not match the reviewed package.
- Extension directory importer is disposed.
- Extension import plan is missing or was already used.
- Extension is already installed
- Extension package changed during copy. Select the folder…
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17).
Data as JSON: /api/errors/dca81bf9461bd81e.
Report an issue: GitHub.
Appendix: source
Thrown at apps/stage-tamagotchi/src/main/services/airi/plugins/host/directory-import.ts:485
}
}
private async assertDestinationAvailable(extensionId: string): Promise<void> {
const destination = join(this.extensionsRoot, extensionId)
if (await this.isExtensionInstalled(extensionId) || await pathExists(destination)) {
throw new Error(`Extension is already installed: ${extensionId}`)
}
}
private async commitPreparedPlan(planId: string): Promise<ManifestEntry> {
const storedPlan = this.plans.get(planId)
if (!storedPlan) {
throw new Error('Extension import plan is missing or was already used.')
}
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)View on GitHub (pinned to 438a067dde)