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

  1. Stop any process modifying the folder, then restart the import: select the folder again (prepare), review, and commit promptly.
  2. Verify manifest.json id still matches the previewed extension; revert edits made after preview.
  3. Exclude the folder from sync/build tools during import, or copy it to a stable location and import from there.
  4. 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

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


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)