{"record":{"id":"188da5044f1fd995","repo":"fullstackhero/dotnet-starter-kit","slug":"impersonation-grant-not-found-impersonationgrantservice","errorCode":null,"errorMessage":"impersonation grant not found","messagePattern":"impersonation grant not found","errorType":"exception","errorClass":"NotFoundException","httpStatus":404,"severity":"error","filePath":"src/Modules/Identity/Modules.Identity/Services/ImpersonationGrantService.cs","lineNumber":85,"sourceCode":"        await db.SaveChangesAsync(ct).ConfigureAwait(false);\n        await SetCachedStatusAsync(grant, GrantState.EndedOrRevoked, ct).ConfigureAwait(false);\n\n        return ToDto(grant);\n    }\n\n    public async Task<ImpersonationGrantDto> RevokeAsync(\n        Guid id,\n        string revokedByUserId,\n        string? revokedByUserName,\n        string? reason,\n        CancellationToken ct = default)\n    {\n        ArgumentNullException.ThrowIfNullOrWhiteSpace(revokedByUserId);\n\n        var grant = await db.ImpersonationGrants\n            .FirstOrDefaultAsync(g => g.Id == id, ct)\n            .ConfigureAwait(false)\n            ?? throw new NotFoundException(\"impersonation grant not found\");\n\n        if (grant.IsTerminal)\n        {\n            // Idempotent — surface the existing terminal state to the caller.\n            return ToDto(grant);\n        }\n\n        grant.Revoke(\n            revokedAtUtc: timeProvider.GetUtcNow().UtcDateTime,\n            revokedByUserId: revokedByUserId,\n            revokedByUserName: revokedByUserName,\n            reason: reason);\n\n        await db.SaveChangesAsync(ct).ConfigureAwait(false);\n        // Write the revocation marker BEFORE returning so a racing request\n        // doesn't slip through with a cached Active marker.\n        await SetCachedStatusAsync(grant, GrantState.EndedOrRevoked, ct).ConfigureAwait(false);\n","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Services/ImpersonationGrantService.cs#L67-L103","documentation":"RevokeAsync in ImpersonationGrantService throws NotFoundException when no ImpersonationGrants row matches the supplied id. The lookup is tenant-scoped via the DbContext; a nonexistent or already-purged grant id produces this error.","triggerScenarios":"Calling RevokeAsync(id, revokedByUserId, ...) with an id that does not exist in the ImpersonationGrants table (typo, wrong environment/DB, or grant deleted by cleanup).","commonSituations":"Client cached a stale grant id after data re-seed; calling revoke against the wrong database/environment; grant purged by a retention job between listing and revoking.","solutions":["Verify the grant id exists (query ImpersonationGrants by id) before revoking","Confirm you are pointed at the correct environment/database","Re-fetch the grants list — the grant may have been purged","Handle NotFoundException in the client and surface 'grant not found' to the user"],"exampleFix":"var grant = await db.ImpersonationGrants.FirstOrDefaultAsync(g => g.Id == id, ct);\nif (grant is null) return Results.NotFound($\"grant {id} not found\");\nawait service.RevokeAsync(id, userId, ct);","handlingStrategy":"try-catch","validationCode":"var exists = await db.ImpersonationGrants.AnyAsync(g => g.Id == grantId, ct);\nif (!exists) return Results.NotFound($\"grant {grantId} not found\");","typeGuard":"bool IsValidGrant(object? grant) => grant is ImpersonationGrant { Id: not Guid.Empty };","tryCatchPattern":"try\n{\n    await impersonationService.RevokeAsync(grantId, adminUserId, ct);\n}\ncatch (NotFoundException)\n{\n    return Results.NotFound(\"impersonation grant not found\");\n}","preventionTips":["Fetch grants immediately before revoking instead of caching ids","Confirm environment/connection string before operating on grant ids","Handle NotFoundException as an expected outcome in revocation UIs"],"tags":["impersonation","not-found","identity"],"backgroundTag":"record-not-found","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}