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
- Recover by performing a fresh explicit login as the original user — the old impersonated token cannot be converted without the actor account.
- Invalidate outstanding impersonation grants when deleting/deactivating users so clients don't attempt to restore a vanished actor.
- 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
- Cap impersonation session duration so actor accounts rarely change mid-session.
- Revoke active impersonation grants when deleting or deactivating users.
- Handle 404 on end-impersonation with a login redirect, not a raw error dialog.
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
- target user not found
- impersonation grant not found
- impersonation grant not found
- Audit record not found.
- Invoice not found.
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)