hashicorp/terraform · error

failed to upload state

Error message

failed to upload state %s: %#v

What it means

Thrown by RemoteClient.Put when bucket.PutObject(c.stateFile, body, options...) fails. The actual OSS PutObject API call returned an error — ACL/SSE/content-length options were applied but the upload was rejected.

Solutions

  1. Grant oss:PutObject on the bucket/object to the configured credentials.
  2. Verify bucket name and endpoint region match.
  3. Check the object key prefix (stateFile) is valid and writable.
  4. If using serverSideEncryption or acl, confirm the bucket allows the requested SSE/ACL.
  5. Retry on transient network errors; inspect the %#v underlying error for the SDK error code.
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: confirm PutObject permission via a head/conditional probe (lightweight).
// At minimum, validate the bucket is reachable and writable in staging.

Try / catch

// Inspect the underlying OSS service error code to decide retry vs surface.
if err := bucket.PutObject(c.stateFile, body, options...); err != nil {
    var se oss.ServiceError
    if errors.As(err, &se) && isRetryableCode(se.Code) { /* backoff + retry */ }
    return fmt.Errorf("failed to upload state %s: %#v", c.stateFile, err)
}

Prevention

When it happens

Trigger: PutObject returns non-nil: bucket does not exist, credentials lack oss:PutObject permission, bucket is read-only or in a locked-down policy, object key (stateFile path) is invalid, or network/endpoint failure.

Common situations: Wrong region endpoint causing bucket-not-found; RAM policy missing oss:PutObject; SSE-AES256 requested but bucket has conflicting encryption config; ACL option conflicts with bucket policy; large state hitting size limits.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/81443eb705c794b3. Report an issue: GitHub.

Appendix: source

Thrown at internal/backend/remote-state/oss/client.go:121

	if err != nil {
		return diags.Append(fmt.Errorf("error getting bucket: %#v", err))
	}

	body := bytes.NewReader(data)

	var options []oss.Option
	if c.acl != "" {
		options = append(options, oss.ACL(oss.ACLType(c.acl)))
	}
	options = append(options, oss.ContentType("application/json"))
	if c.serverSideEncryption {
		options = append(options, oss.ServerSideEncryption("AES256"))
	}
	options = append(options, oss.ContentLength(int64(len(data))))

	if body != nil {
		if err := bucket.PutObject(c.stateFile, body, options...); err != nil {
			return diags.Append(fmt.Errorf("failed to upload state %s: %#v", c.stateFile, err))
		}
	}

	sum := md5.Sum(data)
	if err := c.putMD5(sum[:]); err != nil {
		// if this errors out, we unfortunately have to error out altogether,
		// since the next Get will inevitably fail.
		return diags.Append(fmt.Errorf("failed to store state MD5: %s", err))
	}
	return diags
}

func (c *RemoteClient) Delete() tfdiags.Diagnostics {
	var diags tfdiags.Diagnostics
	bucket, err := c.ossClient.Bucket(c.bucketName)
	if err != nil {
		return diags.Append(fmt.Errorf("error getting bucket %s: %#v", c.bucketName, err))
	}

View on GitHub (pinned to d32a084675)