{"record":{"id":"0cb71f3100ab400f","repo":"duplicati/duplicati","slug":"googlecloudstoragemissingfullcontrolscope","errorCode":"GoogleCloudStorageMissingFullControlScope","errorMessage":"The supplied credentials are missing the full control scope. Please use a service account with full control access.","messagePattern":"The supplied credentials are missing the full control scope\\. Please use a service account with full control access\\.","errorType":"exception","errorClass":"UserInformationException","httpStatus":null,"severity":"error","filePath":"Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs","lineNumber":285,"sourceCode":"        {\r\n            var url = WebApi.GoogleCloudStorage.MetadataUrl(m_bucket, Utility.UrlEncoding.UrlPathEncode(m_prefix + remotename));\r\n\r\n            var metadata = new ObjectMetadata\r\n            {\r\n                retention = new Retention\r\n                {\r\n                    mode = m_retention_policy_mode.ToString(),\r\n                    retainUntilTime = lockUntilUtc\r\n                }\r\n            };\r\n\r\n            try\r\n            {\r\n                if (m_oauth_fullcontrol == null)\r\n                    m_oauth_fullcontrol = m_create_oauth_fullcontrol();\r\n\r\n                if (m_oauth_fullcontrol == null)\r\n                    throw new UserInformationException(Strings.GoogleCloudStorage.MissingFullControlScopeError, \"GoogleCloudStorageMissingFullControlScope\");\r\n\r\n                using var req = await m_oauth_fullcontrol.CreateRequestAsync(url, new HttpMethod(\"PATCH\"), cancellationToken).ConfigureAwait(false);\r\n                req.Content = JsonContent.Create(metadata);\r\n                req.Headers.Add(\"Accept\", \"application/json\");\r\n\r\n                using var resp = await m_oauth_fullcontrol.GetResponseAsync(req, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);\r\n            }\r\n            catch (HttpRequestException hrex)\r\n            {\r\n                if (hrex.StatusCode == HttpStatusCode.NotFound)\r\n                    throw new FileMissingException();\r\n                throw;\r\n            }\r\n        }\r\n\r\n        public Task TestAsync(bool alsoWrite, CancellationToken cancelToken)\r\n            => this.TestBackendAsync(alsoWrite, cancelToken);\r\n\r","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/duplicati/duplicati/blob/3f348be3e33f5d72d414e3ad55839c2ba34dda67/Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs#L267-L303","documentation":"In SetObjectLockUntilAsync, before issuing the PATCH that sets retention, Duplicati builds an OAuth client with the 'full control' scope; if m_create_oauth_fullcontrol() returns null (the configured credentials do not grant devstorage.full_control), it throws UserInformationException code 'GoogleCloudStorageMissingFullControlScope'. Setting object retention metadata requires write access to the bucket/object ACLs and retention, which read-only scopes cannot provide.","triggerScenarios":"The user configured GCS with credentials whose OAuth scope lacks https://www.googleapis.com/auth/devstorage.full_control (e.g. only read_only or read_write), then attempted to set an object-lock / retention policy. m_create_oauth_fullcontrol() returns null because the requested scope is unavailable for those credentials.","commonSituations":"Using a service-account JSON or OAuth flow scoped to read_only/read_write and then enabling --gcs-retention-policy-mode; authorizing via an OAuth consent screen that only requests read scope; reusing a token cached from a lower-scope authorization.","solutions":["Re-authorize the OAuth flow (or the service account) with the devstorage.full_control scope and re-supply credentials.","If using a service-account JSON, confirm its project/role grants roles/storage.objectAdmin (or equivalent) and the scope includes full_control.","Disable --gcs-retention-policy-mode if you do not actually need retention, allowing the lower-scope credentials to work.","Clear any cached OAuth token from the lower-scope authorization before retrying."],"exampleFix":"// before\nif (m_oauth_fullcontrol == null)\n    throw new UserInformationException(Strings.GoogleCloudStorage.MissingFullControlScopeError, \"GoogleCloudStorageMissingFullControlScope\");\n\n// after (name the missing scope so the user knows exactly what to re-grant)\nif (m_oauth_fullcontrol == null)\n    throw new UserInformationException(\n        \"The supplied credentials are missing the devstorage.full_control scope, which is required to set object retention. Re-authorize with full control access.\",\n        \"GoogleCloudStorageMissingFullControlScope\");","handlingStrategy":"validation","validationCode":"// Verify the full-control scope is granted before attempting to set retention\nif (!HasScope(credentials, \"https://www.googleapis.com/auth/devstorage.full_control\"))\n    throw new InvalidOperationException(\"Re-authorize with devstorage.full_control to use object retention.\");","typeGuard":null,"tryCatchPattern":"try { await backend.SetObjectLockUntilAsync(remotename, until, ct); }\ncatch (UserInformationException ex) when (ex.HelpID == \"GoogleCloudStorageMissingFullControlScope\")\n{\n    Console.Error.WriteLine(\"Re-authorize GCS credentials with the full_control scope to set retention.\");\n    throw;\n}","preventionTips":["Authorize OAuth with devstorage.full_control from the start if you plan to use retention.","For service accounts, grant roles/storage.objectAdmin and include the full_control scope.","Do not enable --gcs-retention-policy-mode with read-only/read-write credentials.","Clear cached lower-scope tokens before retrying with the wider scope."],"tags":["gcs","google-cloud-storage","oauth","scope","permissions","retention","configuration"],"backgroundTag":null,"analyzedSha":"3f348be3e33f5d72d414e3ad55839c2ba34dda67","analyzedAt":"2026-08-13T16:48:27.008Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}