fullstackhero/dotnet-starter-kit · error · CustomException
Storage quota exceeded
Error message
Storage quota exceeded ({quotaCheck.CurrentUsage}/{quotaCheck.Limit} bytes). What it means
CustomException with HTTP 507 (Insufficient Storage) thrown when quotas.CheckAsync(tenantId, QuotaResource.StorageBytes, cmd.SizeBytes) reports !Allowed — the tenant's current storage usage plus the new file would exceed the configured StorageBytes limit. Debit happens later at finalize; this is only the pre-check, so the message shows projected usage vs limit.
Solutions
- Free storage space (delete unneeded files) or request a quota/plan increase for the tenant, then retry.
- Audit quota usage accounting: verify finalize/delete paths correctly adjust QuotaResource.StorageBytes so usage is not permanently inflated.
- Show tenants their current usage vs limit in the UI before they attempt uploads.
- Handle HTTP 507 in the client with a clear 'storage full' message instead of a generic error.
Example fix
// before
await requestUploadUrl({ category: "attachment", sizeBytes: 2_000_000_000, ... }); // 507: quota exceeded
// after
const { usage, limit } = await getStorageUsage();
if (usage + file.size > limit) throw new Error("Storage quota exceeded — free up space or upgrade");
await requestUploadUrl({ category: "attachment", sizeBytes: file.size, ... }); Defensive patterns
Strategy: try-catch
Validate before calling
const { usage, limit } = await getTenantStorageUsage();
if (usage + file.size > limit) throw new Error("Tenant storage quota would be exceeded"); Try / catch
try { await requestUploadUrl(cmd); } catch (e) { if (e.status === 507) { showToast("Storage quota exceeded — free up space or upgrade your plan"); } else throw e; } Prevention
- Show tenants current usage vs limit in the storage settings UI.
- Delete stale files promptly so usage stays accurate; verify deletes release quota.
- Handle 507 distinctly in the API client so it isn't shown as a generic 500.
When it happens
Trigger: Requesting an upload URL whose SizeBytes pushes the tenant over its storage quota: many accumulated files, one very large upload, or a tenant plan with a low StorageBytes limit. Also triggered if usage accounting is inflated (files deleted without releasing quota) or a stale limit after a downgrade.
Common situations: Tenants on trial/free plans hitting their cap; failed finalizations leaking quota usage so usage never decreases; admin lowering a plan's storage while tenant usage already exceeds the new limit; users retrying uploads that inflate pending reservations.
Understand the failure class
Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.
Related errors
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/f9a382b82e75f9d5.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Files/Modules.Files/Features/v1/RequestUploadUrl/RequestUploadUrlCommandHandler.cs:74
throw new CustomException(
$"File exceeds max size of {category.MaxBytes} bytes for category '{cmd.Category}'.",
(IEnumerable<string>?)null,
HttpStatusCode.BadRequest);
}
// Authorization: policy must exist and allow the attach.
var policy = policies.Resolve(cmd.OwnerType)
?? throw new ForbiddenException($"No file access policy registered for owner type '{cmd.OwnerType}'.");
if (!await policy.CanAttachAsync(cmd.OwnerId, userId.ToString(), cancellationToken).ConfigureAwait(false))
{
throw new ForbiddenException("Not allowed to attach files to this owner.");
}
// Quota pre-check (no debit yet — debit happens on finalize with actual bytes).
var quotaCheck = await quotas.CheckAsync(tenantId, QuotaResource.StorageBytes, cmd.SizeBytes, cancellationToken).ConfigureAwait(false);
if (!quotaCheck.Allowed)
{
throw new CustomException(
$"Storage quota exceeded ({quotaCheck.CurrentUsage}/{quotaCheck.Limit} bytes).",
(IEnumerable<string>?)null,
(HttpStatusCode)507);
}
// Generate id + storage key + presigned URL.
var id = Guid.CreateVersion7();
var storageKey = StorageKeyBuilder.Build(tenantId, cmd.OwnerType, id, cmd.FileName, DateTimeOffset.UtcNow);
var ttl = TimeSpan.FromMinutes(options.Value.UploadUrlTtlMinutes);
var presigned = await storage.GenerateUploadUrlAsync(storageKey, cmd.ContentType, category.MaxBytes, ttl, cancellationToken).ConfigureAwait(false);
var asset = FileAsset.CreatePending(
id: id,
ownerType: cmd.OwnerType,
ownerId: cmd.OwnerId,
originalFileName: cmd.FileName,
sanitizedFileName: StorageKeyBuilder.Sanitize(cmd.FileName),
contentType: cmd.ContentType,View on GitHub (pinned to 3f2959e683)