{"record":{"id":"f9a382b82e75f9d5","repo":"fullstackhero/dotnet-starter-kit","slug":"storage-quota-exceeded-quotacheck-currentusage-quotacheck","errorCode":null,"errorMessage":"Storage quota exceeded ({quotaCheck.CurrentUsage}/{quotaCheck.Limit} bytes).","messagePattern":"Storage quota exceeded \\((.+?)/(.+?) bytes\\)\\.","errorType":"exception","errorClass":"CustomException","httpStatus":507,"severity":"error","filePath":"src/Modules/Files/Modules.Files/Features/v1/RequestUploadUrl/RequestUploadUrlCommandHandler.cs","lineNumber":74,"sourceCode":"            throw new CustomException(\n                $\"File exceeds max size of {category.MaxBytes} bytes for category '{cmd.Category}'.\",\n                (IEnumerable<string>?)null,\n                HttpStatusCode.BadRequest);\n        }\n\n        // Authorization: policy must exist and allow the attach.\n        var policy = policies.Resolve(cmd.OwnerType)\n            ?? throw new ForbiddenException($\"No file access policy registered for owner type '{cmd.OwnerType}'.\");\n        if (!await policy.CanAttachAsync(cmd.OwnerId, userId.ToString(), cancellationToken).ConfigureAwait(false))\n        {\n            throw new ForbiddenException(\"Not allowed to attach files to this owner.\");\n        }\n\n        // Quota pre-check (no debit yet — debit happens on finalize with actual bytes).\n        var quotaCheck = await quotas.CheckAsync(tenantId, QuotaResource.StorageBytes, cmd.SizeBytes, cancellationToken).ConfigureAwait(false);\n        if (!quotaCheck.Allowed)\n        {\n            throw new CustomException(\n                $\"Storage quota exceeded ({quotaCheck.CurrentUsage}/{quotaCheck.Limit} bytes).\",\n                (IEnumerable<string>?)null,\n                (HttpStatusCode)507);\n        }\n\n        // Generate id + storage key + presigned URL.\n        var id = Guid.CreateVersion7();\n        var storageKey = StorageKeyBuilder.Build(tenantId, cmd.OwnerType, id, cmd.FileName, DateTimeOffset.UtcNow);\n        var ttl = TimeSpan.FromMinutes(options.Value.UploadUrlTtlMinutes);\n        var presigned = await storage.GenerateUploadUrlAsync(storageKey, cmd.ContentType, category.MaxBytes, ttl, cancellationToken).ConfigureAwait(false);\n\n        var asset = FileAsset.CreatePending(\n            id: id,\n            ownerType: cmd.OwnerType,\n            ownerId: cmd.OwnerId,\n            originalFileName: cmd.FileName,\n            sanitizedFileName: StorageKeyBuilder.Sanitize(cmd.FileName),\n            contentType: cmd.ContentType,","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Files/Modules.Files/Features/v1/RequestUploadUrl/RequestUploadUrlCommandHandler.cs#L56-L92","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait requestUploadUrl({ category: \"attachment\", sizeBytes: 2_000_000_000, ... }); // 507: quota exceeded\n// after\nconst { usage, limit } = await getStorageUsage();\nif (usage + file.size > limit) throw new Error(\"Storage quota exceeded — free up space or upgrade\");\nawait requestUploadUrl({ category: \"attachment\", sizeBytes: file.size, ... });","handlingStrategy":"try-catch","validationCode":"const { usage, limit } = await getTenantStorageUsage();\nif (usage + file.size > limit) throw new Error(\"Tenant storage quota would be exceeded\");","typeGuard":null,"tryCatchPattern":"try { await requestUploadUrl(cmd); } catch (e) { if (e.status === 507) { showToast(\"Storage quota exceeded — free up space or upgrade your plan\"); } else throw e; }","preventionTips":["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."],"tags":["quota","storage","files","multitenancy"],"backgroundTag":"file-size-limit-exceeded","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"}