elsa-workflows/elsa-core · error · InvalidOperationException

Adapter ' ' settings migration contains a cycle.

Error message

Adapter '{adapterType}' settings migration contains a cycle.

What it means

As a loop guard, MigrateAsync caps migration steps at 64. Since every valid migration strictly increases the version, exceeding 64 steps can only happen if the migration graph loops (or the invariant check failed), so the service aborts instead of spinning forever.

Solutions

  1. Inspect the registered migrations for the adapter and remove/repair the cycle.
  2. If the chain is legitimately long, split the migration or raise the step cap consciously.
  3. Re-register a clean, strictly-increasing migration set at startup.

Example fix

// before
// cycles 1->2, 2->1
// after
// chain 1->2, 2->3 (strictly increasing)
Defensive patterns

Strategy: try-catch

Try / catch

try { await service.MigrateAsync(type, v, settings); }
catch (InvalidOperationException ex) when (ex.Message.Contains("contains a cycle"))
{ /* dump registered migration graph for diagnosis; abort migration */ }

Prevention

When it happens

Trigger: A migration graph where (adapterType, version) lookups cycle between versions — normally prevented by the ToVersion > version check, so hitting this indicates corrupted registration state or a mutated registry during execution.

Common situations: Custom/buggy registry implementations that return inconsistent migrations between iterations; manually composed migration chains with duplicate overlapping steps; extremely long legitimate chains (>64 versions) in heavily-iterated adapters.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of elsa-workflows/elsa-core@fe9217bdfa (2026-09-13). Data as JSON: /api/errors/91c5092969d25a54. Report an issue: GitHub.

Appendix: source

Thrown at src/modules/Elsa.ExternalAuthentication/Services/AdapterSettingsMigrationService.cs:44

            throw new InvalidOperationException($"The adapter type '{adapterType}' is not installed or deployment-allowed.");

        var currentVersion = adapter.Describe().SettingsVersion;
        if (settingsVersion <= 0 || settingsVersion > currentVersion)
            throw new InvalidOperationException($"Settings version {settingsVersion} is not compatible with adapter '{adapterType}' version {currentVersion}.");
        if (settingsVersion == currentVersion)
            return new(currentVersion, settings.Clone(), false);

        var migrated = settings.Clone();
        var version = settingsVersion;
        var stepCount = 0;
        while (version < currentVersion)
        {
            if (!_migrations.TryGetValue((adapterType, version), out var migration))
                throw new InvalidOperationException($"Adapter '{adapterType}' does not provide a settings migration from version {version}.");
            if (migration.ToVersion <= version || migration.ToVersion > currentVersion)
                throw new InvalidOperationException($"Adapter '{adapterType}' has an invalid settings migration from version {version} to {migration.ToVersion}.");
            if (++stepCount > 64)
                throw new InvalidOperationException($"Adapter '{adapterType}' settings migration contains a cycle.");

            migrated = (await migration.MigrateAsync(migrated, cancellationToken)).Clone();
            version = migration.ToVersion;
        }

        return new(version, migrated, true);
    }

    private static IReadOnlyDictionary<(string AdapterType, int FromVersion), IAdapterSettingsMigration> BuildMigrationIndex(
        IEnumerable<IAdapterSettingsMigration> migrations)
    {
        var result = new Dictionary<(string AdapterType, int FromVersion), IAdapterSettingsMigration>();
        foreach (var migration in migrations)
        {
            if (string.IsNullOrWhiteSpace(migration.AdapterType) || migration.FromVersion <= 0)
                throw new InvalidOperationException("Adapter settings migrations must define an adapter type and a positive source version.");
            if (!result.TryAdd((migration.AdapterType, migration.FromVersion), migration))
                throw new InvalidOperationException($"Adapter '{migration.AdapterType}' registers more than one migration from version {migration.FromVersion}.");

View on GitHub (pinned to fe9217bdfa)