hashicorp/terraform · error

failed to upload state %s: %#v

Error message

failed to upload state %s: %#v

What it means

In RemoteClient.Put() (client.go:120-121), bucket.PutObject(stateFile, body, options...) failed uploading the state JSON to OSS. %#v surfaces the full OSS SDK error (code + request-id). This is the actual network/permissions write failure as opposed to the local Bucket() validation.

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 c9def3e214)

Solutions

  1. Read the %#v detail: NoSuchBucket -> recreate/fix bucket; AccessDenied -> grant oss:PutObject and check RAM role.
  2. Validate acl is one of private/public-read/public-read-write/default (oss.ACLType cast expects a canned value).
  3. If using a proxy, ensure HTTPS egress to the OSS endpoint is allowed.
  4. Re-run terraform apply; PutObject is safe to retry as it overwrites the key atomically.

Example fix

// before
backend "oss" {
  bucket = "tf-state"
  acl = "public"   // invalid canned ACL
}

// after
backend "oss" {
  bucket = "tf-state"
  acl = "private"
}
Defensive patterns

Strategy: retry

Validate before calling

// Validate acl/server_side_encryption values and credential scope before apply.
func validACL(a string) bool {
    switch a {
    case "", "private", "public-read", "public-read-write", "default":
        return true
    }
    return false
}

Try / catch

// Inspect the OSS error code in %#v; retry on transient 5xx/timeout,
// fail fast on AccessDenied/SignatureDoesNotMatch.
if oe, ok := err.(oss.ServiceError); ok {
    switch oe.StatusCode {
    case 500, 502, 503, 408:
        return retry() // PutObject is idempotent overwrite
    default:
        return fail(oe)
    }
}

Prevention

When it happens

Trigger: PutObject returns an error: NoSuchBucket, AccessDenied, RequestTimeout, SignatureDoesNotMatch, slow read/write timeout, or a transient 5xx from OSS. ACL, SSE, Content-Length options are attached so misconfig of acl/server_side_encryption can also trigger it.

Common situations: Bucket deleted after init; credentials rotated/expired; acl value not a valid OSS canned ACL; server_side_encryption=true against a bucket with conflicting KMS config; large state hitting body/timeout limits; network/proxy egress blocked to oss-*.aliyuncs.com.

Related errors


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