fullstackhero/dotnet-starter-kit · error · NotFoundException

original actor not found

Error message

original actor not found

What it means

After extracting act_sub/act_tenant from the token, the handler calls _identityService.BuildClaimsForUserAsync(actorUserId, actorTenantId) to rebuild the original actor's claims. If that returns null — the original user no longer exists (deleted/disabled) or is otherwise unresolvable — it throws this NotFoundException, and no restoration token is issued.

Solutions

  1. Recover by performing a fresh explicit login as the original user — the old impersonated token cannot be converted without the actor account.
  2. Invalidate outstanding impersonation grants when deleting/deactivating users so clients don't attempt to restore a vanished actor.
  3. Verify the actor's tenant and user ID in the token's act_sub/act_tenant claims match an existing account (check the users table for that tenant).

Example fix

// before
await api.post("/impersonation/end"); // 404: actor deleted while impersonating

// after
try {
  await api.post("/impersonation/end");
} catch (err) {
  if (err.status === 404) {
    await auth.fullLogin(); // fall back to a fresh login
  }
}
Defensive patterns

Strategy: fallback

Validate before calling

// before ending, confirm the actor account still exists (if the API exposes it)
var actorOk = await userApi.ExistsAsync(session.actorUserId);
if (!actorOk) { await auth.fullLogin(); return; }

Type guard

bool actorStillExists(UserLookupResult? r) => r is { Exists: true };

Try / catch

try
{
    await api.post("/impersonation/end");
}
catch (NotFoundException)
{
    // original actor deleted/disabled: only recovery is a fresh login
    await auth.fullLogin();
    notify("Your original account could not be restored; please sign in again.");
}

Prevention

When it happens

Trigger: Ending impersonation when the original actor account was deleted or deactivated between StartImpersonation and EndImpersonation, or when act_sub references a user in a tenant where the identity service cannot resolve them.

Common situations: Long-running impersonation sessions during which an admin deleted the operator's account; test/staging tokens referencing users pruned by a cleanup job; tenant data restored from a backup missing the actor.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/2880ce682723ca30. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Impersonation/EndImpersonation/EndImpersonationCommandHandler.cs:96

        {
            try
            {
                await _grantService.MarkEndedByJtiAsync(jti, cancellationToken).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex,
                    "Failed to mark impersonation grant ended for jti={Jti}. Actor swap will still proceed.",
                    jti);
            }
        }

        var actorClaimsResult = await _identityService
            .BuildClaimsForUserAsync(actorUserId, actorTenantId, cancellationToken);

        if (actorClaimsResult is null)
        {
            throw new NotFoundException("original actor not found");
        }

        var (subject, actorClaims) = actorClaimsResult.Value;

        var token = await _tokenService.IssueAsync(subject, actorClaims, actorTenantId, cancellationToken);
        await _identityService.StoreRefreshTokenAsync(subject, token.RefreshToken, token.RefreshTokenExpiresAt, cancellationToken);

        await _securityAudit.ImpersonationEndedAsync(
            actorUserId: actorUserId,
            actorTenantId: actorTenantId,
            targetUserId: impersonatedUserId,
            targetTenantId: impersonatedTenantId,
            clientId: _requestContext.ClientId ?? "unknown",
            ct: cancellationToken);

        if (_logger.IsEnabled(LogLevel.Information))
        {
            _logger.LogInformation(

View on GitHub (pinned to 3f2959e683)