kopia/kopia · error

repository will remain locked until index differences are…

Error message

repository will remain locked until index differences are resolved

What it means

During `kopia repository upgrade`, validateAction logs all mismatch messages produced by CheckIndexInfo and wraps the underlying validation error with 'repository will remain locked until index differences are resolved'. The upgrade validation phase detected index differences (e.g. undeleted index blobs or pending epoch work) that make it unsafe to proceed, so the command aborts while the repository upgrade lock stays in place.

Solutions

  1. Wait for the required index-blob deletion delay (or run `kopia index optimize` / restart with all clients connected) so old index blobs get deleted, then retry the upgrade validate step.
  2. Check the logged mismatch messages above the error (CheckIndexInfo diagnostics) and address each listed index difference.
  3. Ensure all kopia clients are upgraded/connected and drained so no new index blobs appear during validation.
  4. As a last resort, revoke the lock with `kopia repository upgrade force-revert` (--force) and restart the upgrade after cleanup.

Example fix

// before
kopia repository upgrade --io-drain-timeout=5s
// after: wait for index blobs to become deletable and re-run validation
kopia index optimize
kopia repository upgrade --io-drain-timeout=30m --advance-notice=24h
Defensive patterns

Strategy: validation

Validate before calling

// before upgrading, ensure index differences are resolved
const mismatches = await runKopia(['repository', 'upgrade', 'validate', '--dry-run']);
if (mismatches.exitCode !== 0) {
  throw new Error(`Index differences remain: ${mismatches.stderr}. Run 'kopia index optimize' and retry.`);
}

Try / catch

try {
  await upgradeRepository();
} catch (e) {
  if (/index differences are resolved/.test(e.message)) {
    await runKopia(['index', 'optimize']);
    await retry(upgradeRepository, { retries: 1 });
  }
}

Prevention

When it happens

Trigger: Running `kopia repository upgrade` (or `repository upgrade validate`) when CheckIndexInfo finds differences between index infos: direct-index blobs that should have been deleted, or mutable-index contents not yet migrated/compacted. validateAction returns errors.Wrap(err, ...) at cli/command_repository_upgrade.go:196.

Common situations: Upgrading to the epoch manager while some connected clients still wrote recently (index blobs not yet aged out); running the upgrade too soon after the io-drain window without all clients having flushed; interrupted previous upgrades leaving index blobs behind.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of kopia/kopia@82495e54b5 (2026-09-07). Data as JSON: /api/errors/d88c8bce1d7a28f3. Report an issue: GitHub.

Appendix: source

Thrown at cli/command_repository_upgrade.go:196

		msgs = append(msgs, fmt.Sprintf("lop-sided index entries for contentID %q at blob %q", contentID, iep1.PackBlobID))
	}

	// no msgs means the check passed without finding anything wrong
	if len(msgs) == 0 {
		log(ctx).Info("index validation succeeded")
		return nil
	}

	// otherwise there's a problem somewhere ... log the problems
	log(ctx).Error("inconsistencies found in migrated index:")

	for _, m := range msgs {
		log(ctx).Error(m)
	}

	// and return an error that states something's wrong.
	return errors.Wrap(err, "repository will remain locked until index differences are resolved")
}

// CheckIndexInfo compare two index infos.  If a mismatch exists, return an error with diagnostic information.
func CheckIndexInfo(i0, i1 content.Info) []string {
	var q []string

	switch {
	case i0.FormatVersion != i1.FormatVersion:
		q = append(q, fmt.Sprintf("mismatched FormatVersions: %v %v", i0.FormatVersion, i1.FormatVersion))
	case i0.OriginalLength != i1.OriginalLength:
		q = append(q, fmt.Sprintf("mismatched OriginalLengths: %v %v", i0.OriginalLength, i1.OriginalLength))
	case i0.PackBlobID != i1.PackBlobID:
		q = append(q, fmt.Sprintf("mismatched PackBlobIDs: %v %v", i0.PackBlobID, i1.PackBlobID))
	case i0.PackedLength != i1.PackedLength:
		q = append(q, fmt.Sprintf("mismatched PackedLengths: %v %v", i0.PackedLength, i1.PackedLength))
	case i0.PackOffset != i1.PackOffset:
		q = append(q, fmt.Sprintf("mismatched PackOffsets: %v %v", i0.PackOffset, i1.PackOffset))
	case i0.EncryptionKeyID != i1.EncryptionKeyID:

View on GitHub (pinned to 82495e54b5)