{"record":{"id":"f524c916b5c5432e","repo":"kopia/kopia","slug":"unexpected-error","errorCode":null,"errorMessage":"unexpected error","messagePattern":"unexpected error","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/providervalidation/providervalidation.go","lineNumber":137,"sourceCode":"\t}()\n\n\tuberPrefix := blob.ID(\"z\" + uuid.NewString())\n\tdefer cleanupAllBlobs(ctx, st[0], uberPrefix)\n\n\tprefix1 := uberPrefix + \"a\"\n\tprefix2 := uberPrefix + \"b\"\n\n\tlog(ctx).Info(\"Validating storage capacity and usage\")\n\n\tc, err := st.pickOne().GetCapacity(ctx)\n\n\tswitch {\n\tcase errors.Is(err, blob.ErrNotAVolume):\n\t\t// This is okay. We expect some implementations to not support this method.\n\tcase c.FreeB > c.SizeB:\n\t\treturn errors.Errorf(\"expected volume's free space (%dB) to be at most volume size (%dB)\", c.FreeB, c.SizeB)\n\tcase err != nil:\n\t\treturn errors.Wrapf(err, \"unexpected error\")\n\t}\n\n\tlog(ctx).Info(\"Validating blob list responses\")\n\n\tif err := verifyBlobCount(ctx, st.pickOne(), uberPrefix, 0); err != nil {\n\t\treturn errors.Wrap(err, \"invalid blob count\")\n\t}\n\n\tlog(ctx).Info(\"Validating non-existent blob responses\")\n\n\tvar out gather.WriteBuffer\n\tdefer out.Close()\n\n\t// read non-existent full blob\n\tif err := st.pickOne().GetBlob(ctx, prefix1+\"1\", 0, -1, &out); !errors.Is(err, blob.ErrBlobNotFound) {\n\t\treturn errors.Errorf(\"got unexpected error when reading non-existent blob: %v\", err)\n\t}\n","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/kopia/kopia/blob/82495e54b584c1ef6073c9e1be048f57f8aef078/internal/providervalidation/providervalidation.go#L119-L155","documentation":"ValidateProvider's capacity check wraps any unexpected GetCapacity error with the generic message 'unexpected error'. It is used when the storage implementation returns an error other than the tolerated blob.ErrNotAVolume while reporting volume capacity.","triggerScenarios":"st.pickOne().GetCapacity(ctx) fails with a real error (auth failure, network timeout, throttling, API error) rather than returning a Capacity or blob.ErrNotAVolume.","commonSituations":"Expired storage credentials hitting the provider's capacity/statistics API; Azure requests rejected due to missing permission to query account metadata; transient network failure during validation.","solutions":["Inspect the wrapped cause — it contains the actual GetCapacity failure from the provider.","Verify credentials have permission for account/capacity metadata APIs.","Retry after transient network issues resolve.","If the backend genuinely lacks capacity support, make its GetCapacity return blob.ErrNotAVolume instead of a generic error."],"exampleFix":"// before\nreturn blob.Capacity{}, errors.New(\"not implemented\")\n// after\nreturn blob.Capacity{}, blob.ErrNotAVolume","handlingStrategy":"try-catch","validationCode":"if _, err := st.GetCapacity(ctx); err != nil && !errors.Is(err, blob.ErrNotAVolume) {\n    return fmt.Errorf(\"capacity API unavailable: %w\", err)\n}","typeGuard":"func isNotAVolume(err error) bool {\n    return errors.Is(err, blob.ErrNotAVolume)\n}","tryCatchPattern":"if err := providervalidation.ValidateProvider(ctx, st, opts); err != nil {\n    var capErr = strings.Contains(err.Error(), \"unexpected error\")\n    if capErr && isAuthErr(errors.Unwrap(err)) {\n        refreshCredentials(); return retry()\n    }\n    return err\n}","preventionTips":["Ensure credentials permit the provider's account/capacity metadata API","Return blob.ErrNotAVolume from GetCapacity when unsupported, never a generic error","Refresh credentials before long validation runs","Retry transient network failures during validation"],"tags":["capacity","validation","blob-storage","api-error"],"backgroundTag":"api-error-response","analyzedSha":"82495e54b584c1ef6073c9e1be048f57f8aef078","analyzedAt":"2026-09-07T20:35:21.689Z","contentChangedAt":"2026-09-07T20:35:21.689Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}