duplicati/duplicati · error · UserInformationException

GoogleCloudStorageMissingFullControlScope

GoogleCloudStorageMissingFullControlScope

Error message

The supplied credentials are missing the full control scope. Please use a service account with full control access.

What it means

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.

Source

Thrown at Duplicati/Library/Backend/GoogleServices/GoogleCloudStorage.cs:285

        {
            var url = WebApi.GoogleCloudStorage.MetadataUrl(m_bucket, Utility.UrlEncoding.UrlPathEncode(m_prefix + remotename));

            var metadata = new ObjectMetadata
            {
                retention = new Retention
                {
                    mode = m_retention_policy_mode.ToString(),
                    retainUntilTime = lockUntilUtc
                }
            };

            try
            {
                if (m_oauth_fullcontrol == null)
                    m_oauth_fullcontrol = m_create_oauth_fullcontrol();

                if (m_oauth_fullcontrol == null)
                    throw new UserInformationException(Strings.GoogleCloudStorage.MissingFullControlScopeError, "GoogleCloudStorageMissingFullControlScope");

                using var req = await m_oauth_fullcontrol.CreateRequestAsync(url, new HttpMethod("PATCH"), cancellationToken).ConfigureAwait(false);
                req.Content = JsonContent.Create(metadata);
                req.Headers.Add("Accept", "application/json");

                using var resp = await m_oauth_fullcontrol.GetResponseAsync(req, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
            }
            catch (HttpRequestException hrex)
            {
                if (hrex.StatusCode == HttpStatusCode.NotFound)
                    throw new FileMissingException();
                throw;
            }
        }

        public Task TestAsync(bool alsoWrite, CancellationToken cancelToken)
            => this.TestBackendAsync(alsoWrite, cancelToken);

View on GitHub (pinned to 3f348be3e3)

Solutions

  1. Re-authorize the OAuth flow (or the service account) with the devstorage.full_control scope and re-supply credentials.
  2. If using a service-account JSON, confirm its project/role grants roles/storage.objectAdmin (or equivalent) and the scope includes full_control.
  3. Disable --gcs-retention-policy-mode if you do not actually need retention, allowing the lower-scope credentials to work.
  4. Clear any cached OAuth token from the lower-scope authorization before retrying.

Example fix

// before
if (m_oauth_fullcontrol == null)
    throw new UserInformationException(Strings.GoogleCloudStorage.MissingFullControlScopeError, "GoogleCloudStorageMissingFullControlScope");

// after (name the missing scope so the user knows exactly what to re-grant)
if (m_oauth_fullcontrol == null)
    throw new UserInformationException(
        "The supplied credentials are missing the devstorage.full_control scope, which is required to set object retention. Re-authorize with full control access.",
        "GoogleCloudStorageMissingFullControlScope");
Defensive patterns

Strategy: validation

Validate before calling

// Verify the full-control scope is granted before attempting to set retention
if (!HasScope(credentials, "https://www.googleapis.com/auth/devstorage.full_control"))
    throw new InvalidOperationException("Re-authorize with devstorage.full_control to use object retention.");

Try / catch

try { await backend.SetObjectLockUntilAsync(remotename, until, ct); }
catch (UserInformationException ex) when (ex.HelpID == "GoogleCloudStorageMissingFullControlScope")
{
    Console.Error.WriteLine("Re-authorize GCS credentials with the full_control scope to set retention.");
    throw;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of duplicati/duplicati@3f348be3e3 (2026-08-13). Data as JSON: /api/errors/0cb71f3100ab400f. Report an issue: GitHub.