hashicorp/terraform · error

uploadSinglePartObject: data is empty

Error message

uploadSinglePartObject: data is empty

What it means

Defensive guard in uploadSinglePartObject: it rejects any call where the data slice is empty. State writes should always carry serialized state; an empty slice indicates a caller bug upstream rather than a legitimate write.

Solutions

  1. Ensure the state serializer always produces non-empty bytes before calling Put.
  2. For initial empty state, rely on StateMgr's init flow (it writes states.NewState(), which is non-empty JSON) rather than calling Put([]byte{}).
  3. If hit in custom code, debug why len(data)==0 upstream — this guard means the precondition is wrong.

Example fix

// before: caller uploads empty bytes
client.Put([]byte{})  // -> "uploadSinglePartObject: data is empty"
// after: write a proper (possibly empty) state object
empty := states.NewState()
var buf bytes.Buffer
json.NewEncoder(&buf).Encode(empty)
client.Put(buf.Bytes())
Defensive patterns

Strategy: validation

Validate before calling

// Reject empty payloads upstream, before reaching uploadSinglePartObject
func putState(c *RemoteClient, data []byte) tfdiags.Diagnostics {
    if len(data) == 0 { return tfdiags.Diagnostics{}.Append(fmt.Errorf("refusing to upload empty state")) }
    return c.Put(data)
}

Prevention

When it happens

Trigger: Caller passed a nil/zero-length byte slice to uploadSinglePartObject; the state serializer returned no bytes; a code path that should have used the empty-state init flow in StateMgr instead tried to Put nothing.

Common situations: Bug in a fork that produces empty serialized state; test fixtures calling Put([]byte{}); a refactor that bypassed the `state == nil -> states.NewState()` init in backend_state.go:94.

Related errors


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

Appendix: source

Thrown at internal/backend/remote-state/oci/client.go:165

		if err != nil && dataSize <= MaxFilePartSize {
			logger.Error(fmt.Sprintf("Multipart upload failed, falling back to single part upload: %v", err))
			err = c.uploadSinglePartObject(ctx, data, sum[:])
		}
	} else {
		err = c.uploadSinglePartObject(ctx, data, sum[:])
	}
	if err != nil {
		return diags.Append(err)
	}

	return diags
}

func (c *RemoteClient) uploadSinglePartObject(ctx context.Context, data, sum []byte) error {
	logger := ctx.Value("logger").(hclog.Logger).Named("singlePartUpload")
	logger.Info("Uploading single part object")
	if len(data) == 0 {
		return fmt.Errorf("uploadSinglePartObject: data is empty")
	}

	contentType := "application/json"

	putRequest := objectstorage.PutObjectRequest{
		ContentType:   common.String(contentType),
		NamespaceName: common.String(c.namespace),
		ObjectName:    common.String(c.path),
		BucketName:    common.String(c.bucketName),
		PutObjectBody: io.NopCloser(bytes.NewReader(data)),
		ContentMD5:    common.String(base64.StdEncoding.EncodeToString(sum)),
		RequestMetadata: common.RequestMetadata{
			RetryPolicy: getDefaultRetryPolicy(),
		},
	}

	// Handle encryption settings
	if c.kmsKeyID != "" {

View on GitHub (pinned to d32a084675)