itsfatduck/optimizerDuck · error · StepExecutionException

Registry verify failed at

Error message

Registry verify failed at {Path}:{Name}: expected '{Value}', actual '{actual}'

What it means

Thrown by VerifyRestore when the value read back after a RestorePrevious revert does not equal the originally captured Value for the given RegistryValueKind. The restore 'succeeded' per the write call, but the on-disk registry value differs from the backup.

Solutions

  1. Compare the Kind field in the revert JSON with the actual value kind shown in regedit and correct it.
  2. Re-run the revert so it overwrites the intervening change.
  3. Identify what process rewrote the value (Event Viewer / Process Monitor) and disable it before reverting.
  4. Set the value manually to the recorded backup Value and remove the revert file.

Example fix

// before
if (!ValuesEqual(actual, Value, Kind))
    throw new StepExecutionException($"Registry verify failed at {Path}:{Name}: expected '{Value}', actual '{actual}'", null);
// after: retry the write once before failing
if (!ValuesEqual(actual, Value, Kind))
{
    RegistryService.Write(opCall, new RegistryItem(Path, Name!, Value, Kind));
    RegistryService.TryReadValue(item, out actual, opCall.Logger);
}
Defensive patterns

Strategy: validation

Validate before calling

// before revert, sanity-check backup against current value
using var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(pathAfterRoot);
var current = key?.GetValue(name);
if (current == null) logger.LogWarning("Value missing before restore of {Path}:{Name}", path, name);

Type guard

bool KindMatches(RegistryValueKind recorded, object current) => recorded switch
{
    RegistryValueKind.DWord => current is int,
    RegistryValueKind.QWord => current is long,
    RegistryValueKind.String or RegistryValueKind.ExpandString => current is string,
    RegistryValueKind.MultiString => current is string[],
    RegistryValueKind.Binary => current is byte[],
    _ => true
};

Try / catch

try { await step.ExecuteAsync(shell, logger); }
catch (StepExecutionException ex) when (ex.Message.Contains("expected '"))
{
    logger.LogError("Value mismatch after restore; a concurrent writer likely changed {Path}", step.Path);
}

Prevention

When it happens

Trigger: RevertAction.RestorePrevious with Value != null and RegistryValues.Equal(actual, Value, Kind) returning false — e.g. DWORD 0x1 vs '1' string kind mismatch, ExpandString vs String round-trip differences, or a concurrent process overwriting the value between write and read-back.

Common situations: Kind recorded in the revert JSON differs from the value's actual registry kind (e.g. optimization wrote REG_SZ but backup says REG_DWORD); environment-variable expansion changing ExpandString content; race with a service resetting the same value.

Related errors


AI-assisted analysis of itsfatduck/optimizerDuck@36acf585ae (2026-09-13). Data as JSON: /api/errors/b5b569a1c08c5bf4. Report an issue: GitHub.

Appendix: source

Thrown at optimizerDuck/Domain/Revert/Steps/RegistryRevertStep.cs:264

            CreatedSubKeys = createdSubKeys,
            SubSteps = subSteps,
        };
    }

    private void VerifyRestore(OpCall call)
    {
        switch (Action)
        {
            case RevertAction.RestorePrevious when Value != null:
            {
                var item = new RegistryItem(Path, Name!);
                if (!RegistryService.TryReadValue(item, out var actual, call.Logger))
                    throw new StepExecutionException(
                        $"Registry verify failed at {Path}:{Name}: could not read the value.",
                        null
                    );
                if (!ValuesEqual(actual, Value, Kind))
                    throw new StepExecutionException(
                        $"Registry verify failed at {Path}:{Name}: expected '{Value}', actual '{actual}'",
                        null
                    );
                break;
            }
            case RevertAction.NoPreviousValue:
            case RevertAction.RestorePrevious:
            {
                // a failed read is not "absent".
                if (
                    !RegistryService.TryReadValue(
                        new RegistryItem(Path, Name!),
                        out var actual,
                        call.Logger
                    )
                )
                    throw new StepExecutionException(
                        $"Registry verify failed at {Path}:{Name}: could not read the value.",

View on GitHub (pinned to 36acf585ae)