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

  1. Free storage space (delete unneeded files) or request a quota/plan increase for the tenant, then retry.
  2. Audit quota usage accounting: verify finalize/delete paths correctly adjust QuotaResource.StorageBytes so usage is not permanently inflated.
  3. Show tenants their current usage vs limit in the UI before they attempt uploads.
  4. 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

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)