hashicorp/terraform · error

failed to save file to

Error message

failed to save file to %v: %v

What it means

Returned by putObject() when the COS SDK returns a nil response pointer from Object.Put. Identical in shape to error 201 but on the write path: no HTTP response was ever received, indicating a transport-level failure to reach COS for the PUT. The original SDK error is wrapped.

Solutions

  1. Inspect the wrapped error and the DEBUG line `putObject <file>: error:`.
  2. Verify TENCENTCLOUD_SECRETID/SECRETKEY and the bucket region/endpoint.
  3. Test outbound HTTPS connectivity from the host to the COS endpoint.
  4. Retry once transient issues are excluded; for large states, ensure no middlebox is truncating PUT bodies.

Example fix

// before: missing region, PUT cannot be signed/sent
backend "cos" { bucket="tf-state"; secret_id="..."; secret_key="..." }
// after: include region
backend "cos" { bucket="tf-state"; region="ap-shanghai"; secret_id="..."; secret_key="..." }
Defensive patterns

Strategy: retry

Validate before calling

// Validate write-path connectivity before applying
func canWriteObject(ctx context.Context, client *cos.Client, key string) error {
    // probe with a HEAD on the bucket; for create semantics, attempt a tiny PUT to a probe key
    rsp, err := client.Object.Put(ctx, key+".probe", bytes.NewReader([]byte("x")), nil)
    if rsp == nil { return fmt.Errorf("transport failure writing to COS: %w", err) }
    rsp.Body.Close()
    if rsp.StatusCode >= 400 { return fmt.Errorf("write probe failed: %d", rsp.StatusCode) }
    client.Object.Delete(ctx, key+".probe")
    return nil
}

Type guard

func isNilResponse(rsp *cos.Response) bool { return rsp == nil }

Try / catch

// On PUT with nil response, retry with exponential backoff (transport-level):
backoff.Retry(func() error {
    rsp, err := client.Object.Put(ctx, key, r, opt)
    if rsp == nil { return err } // retryable
    defer rsp.Body.Close()
    if err != nil { return backoff.Permanent(err) }
    return nil
}, backoff.NewExponentialBackOff())

Prevention

When it happens

Trigger: c.cosClient.Object.Put(...) returns (nil, err) — network unreachable, invalid endpoint, DNS failure, TLS handshake failure, credentials missing so the SDK cannot sign, or context cancellation before the request completed.

Common situations: Backend configured without a valid `region`/`endpoint`; rotated Tencent Cloud keys not refreshed in env/backend; running Terraform in a sandboxed CI runner without outbound access to `cos.<region>.myqcloud.com`; large state PUT exceeding a network middlebox limit and the connection being reset.

Related errors


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

Appendix: source

Thrown at internal/backend/remote-state/cos/client.go:247

		ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{
			XCosMetaXXX: &http.Header{
				"X-Cos-Meta-Md5": []string{fmt.Sprintf("%x", md5.Sum(data))},
			},
		},
		ACLHeaderOptions: &cos.ACLHeaderOptions{
			XCosACL: c.acl,
		},
	}

	if c.encrypt {
		opt.ObjectPutHeaderOptions.XCosServerSideEncryption = "AES256"
	}

	r := bytes.NewReader(data)
	rsp, err := c.cosClient.Object.Put(c.cosContext, cosFile, r, opt)
	if rsp == nil {
		log.Printf("[DEBUG] putObject %s: error: %v", cosFile, err)
		return fmt.Errorf("failed to save file to %v: %v", cosFile, err)
	}
	defer rsp.Body.Close()

	log.Printf("[DEBUG] putObject %s: code: %d, error: %v", cosFile, rsp.StatusCode, err)
	if err != nil {
		return fmt.Errorf("failed to save file to %v: %v", cosFile, err)
	}

	return nil
}

// deleteObject delete remote object
func (c *remoteClient) deleteObject(cosFile string) error {
	rsp, err := c.cosClient.Object.Delete(c.cosContext, cosFile)
	if rsp == nil {
		log.Printf("[DEBUG] deleteObject %s: error: %v", cosFile, err)
		return fmt.Errorf("failed to delete file %v: %v", cosFile, err)
	}

View on GitHub (pinned to d32a084675)