itsfatduck/optimizerDuck · error · StepExecutionException

Registry verify failed at

Error message

Registry verify failed at {Path}: expected key to be absent, but it still exists

What it means

Thrown by VerifyRestore during RevertAction.DeleteKey when the key still exists after DeleteSubKeyTree reported success. DeleteSubKeyTree only removes empty key trees; any residual value or subkey (or an open handle) leaves the key in place, so verification fails.

Solutions

  1. Check for values/subkeys still under the key in regedit and delete them manually, then retry.
  2. Re-run elevated — access-denied children can silently block tree deletion.
  3. Stop the process holding handles on the key (Process Explorer, 'Find Handle').
  4. If the tree exceeds the backup caps, delete via PowerShell Remove-Item -Recurse and remove the revert file.

Example fix

// before
if (deleteExists)
    throw new StepExecutionException($"Registry verify failed at {Path}: expected key to be absent, but it still exists", null);
// after: retry the tree delete once before failing
if (deleteExists)
{
    RegistryService.DeleteSubKeyTree(opCall, new RegistryItem(Path));
    RegistryService.TryKeyExists(new RegistryItem(Path), out deleteExists, call.Logger);
    if (deleteExists) throw new StepExecutionException($"...expected key to be absent, but it still exists", null);
}
Defensive patterns

Strategy: retry

Validate before calling

// pre-check tree contents depth/size expectations
using var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(pathAfterRoot);
int childCount = key?.GetSubKeyNames().Length ?? 0;
if (childCount > 100) logger.LogWarning("Large key tree at {Path}; deletion may exceed backup caps", path);

Type guard

bool IsDeleteKeyStillPresent(StepExecutionException ex) => ex.Message.Contains("expected key to be absent, but it still exists");

Try / catch

try { await step.ExecuteAsync(shell, logger); }
catch (StepExecutionException ex) when (ex.Message.Contains("still exists"))
{
    logger.LogWarning("Key still present after DeleteSubKeyTree; retrying once elevated");
    await step.ExecuteAsync(shell, logger);
}

Prevention

When it happens

Trigger: DeleteSubKeyTree(Path) returned Ok but TryKeyExists(Path) finds the key — a subkey/value was recreated after deletion, a process holds an open handle, or the app's DeleteSubKeyTree backup limits (max depth 15 / 5000 items) skipped part of the tree.

Common situations: Deep key trees exceeding the revert-step depth/item caps; running services keeping handles on the key; another component recreating the key immediately; non-empty keys when only subkey-less trees can actually be deleted.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

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

                    throw new StepExecutionException(
                        $"Registry verify failed at {Path}: expected key to exist, but it was missing",
                        null
                    );
                break;
            case RevertAction.DeleteKey:
                if (
                    !RegistryService.TryKeyExists(
                        new RegistryItem(Path),
                        out var deleteExists,
                        call.Logger
                    )
                )
                    throw new StepExecutionException(
                        $"Registry verify failed at {Path}: could not query the key.",
                        null
                    );
                if (deleteExists)
                    throw new StepExecutionException(
                        $"Registry verify failed at {Path}: expected key to be absent, but it still exists",
                        null
                    );
                break;
            default:
                break;
        }
    }

    private static bool ValuesEqual(object? actual, object? expected, RegistryValueKind kind) =>
        RegistryValues.Equal(actual, expected, kind);

    private async Task<bool> ExecuteSubStepsAsync(ShellService shell, ILogger logger)
    {
        if (SubSteps == null)
            return true;
        foreach (var step in SubSteps)
        {

View on GitHub (pinned to 36acf585ae)