diff --git a/commands/response.go b/commands/response.go
index a720a8214542d5a6a42cd41f6be95bf700343e0b..657dd0403e2a647d80f708e89900016337b77cb7 100644
--- a/commands/response.go
+++ b/commands/response.go
@@ -15,8 +15,9 @@ type ErrorType uint
 
 // ErrorTypes convey what category of error ocurred
 const (
-	ErrNormal ErrorType = iota // general errors
-	ErrClient                  // error was caused by the client, (e.g. invalid CLI usage)
+	ErrNormal         ErrorType = iota // general errors
+	ErrClient                          // error was caused by the client, (e.g. invalid CLI usage)
+	ErrImplementation                  // programmer error in the server
 	// TODO: add more types of errors for better error-specific handling
 )
 
diff --git a/core/commands/unixfs/ls.go b/core/commands/unixfs/ls.go
index 58ad2f811efb4eb8e4292e2b700ff539914fa839..9d9cdbdbb553cfe5566253635022cabc0eac5eef 100644
--- a/core/commands/unixfs/ls.go
+++ b/core/commands/unixfs/ls.go
@@ -59,40 +59,66 @@ directories, the child size is the IPFS link size.
 
 		output := make([]*LsObject, len(paths))
 		for i, fpath := range paths {
-			dagnode, err := core.Resolve(req.Context().Context, node, path.Path(fpath))
+			ctx := req.Context().Context
+			merkleNode, err := core.Resolve(ctx, node, path.Path(fpath))
 			if err != nil {
 				res.SetError(err, cmds.ErrNormal)
 				return
 			}
 
-			output[i] = &LsObject{
-				Argument: fpath,
-				Links:    make([]LsLink, len(dagnode.Links)),
+			unixFSNode, err := unixfs.FromBytes(merkleNode.Data)
+			if err != nil {
+				res.SetError(err, cmds.ErrNormal)
+				return
 			}
-			for j, link := range dagnode.Links {
-				ctx, cancel := context.WithTimeout(context.TODO(), time.Minute)
-				defer cancel()
-				link.Node, err = link.GetNode(ctx, node.DAG)
-				if err != nil {
-					res.SetError(err, cmds.ErrNormal)
-					return
-				}
-				d, err := unixfs.FromBytes(link.Node.Data)
+
+			output[i] = &LsObject{}
+
+			t := unixFSNode.GetType()
+			switch t {
+			default:
+				res.SetError(fmt.Errorf("unrecognized type: %s", t), cmds.ErrImplementation)
+				return
+			case unixfspb.Data_File:
+				key, err := merkleNode.Key()
 				if err != nil {
 					res.SetError(err, cmds.ErrNormal)
 					return
 				}
-				lsLink := LsLink{
-					Name: link.Name,
-					Hash: link.Hash.B58String(),
-					Type: d.GetType(),
+				output[i].Links = []LsLink{LsLink{
+					Name: fpath,
+					Hash: key.String(),
+					Type: t,
+					Size: unixFSNode.GetFilesize(),
+				}}
+			case unixfspb.Data_Directory:
+				output[i].Argument = fpath
+				output[i].Links = make([]LsLink, len(merkleNode.Links))
+				for j, link := range merkleNode.Links {
+					getCtx, cancel := context.WithTimeout(context.TODO(), time.Minute)
+					defer cancel()
+					link.Node, err = link.GetNode(getCtx, node.DAG)
+					if err != nil {
+						res.SetError(err, cmds.ErrNormal)
+						return
+					}
+					d, err := unixfs.FromBytes(link.Node.Data)
+					if err != nil {
+						res.SetError(err, cmds.ErrNormal)
+						return
+					}
+					lsLink := LsLink{
+						Name: link.Name,
+						Hash: link.Hash.B58String(),
+						Type: d.GetType(),
+					}
+					if lsLink.Type == unixfspb.Data_File {
+						lsLink.Size = d.GetFilesize()
+					} else {
+						lsLink.Size = link.Size
+					}
+					output[i].Links[j] = lsLink
 				}
-				if lsLink.Type == unixfspb.Data_File {
-					lsLink.Size = d.GetFilesize()
-				} else {
-					lsLink.Size = link.Size
-				}
-				output[i].Links[j] = lsLink
 			}
 		}
 
@@ -104,16 +130,23 @@ directories, the child size is the IPFS link size.
 			output := res.Output().(*LsOutput)
 			buf := new(bytes.Buffer)
 			w := tabwriter.NewWriter(buf, 1, 2, 1, ' ', 0)
-			for _, object := range output.Objects {
-				if len(output.Objects) > 1 {
+			lastObjectDirHeader := false
+			for i, object := range output.Objects {
+				if len(output.Objects) > 1 && object.Argument != "" {
+					if i > 0 {
+						fmt.Fprintln(w)
+					}
 					fmt.Fprintf(w, "%s:\n", object.Argument)
+					lastObjectDirHeader = true
+				} else {
+					if lastObjectDirHeader {
+						fmt.Fprintln(w)
+					}
+					lastObjectDirHeader = false
 				}
 				for _, link := range object.Links {
 					fmt.Fprintf(w, "%s\n", link.Name)
 				}
-				if len(output.Objects) > 1 {
-					fmt.Fprintln(w)
-				}
 			}
 			w.Flush()
 
diff --git a/test/sharness/t0200-unixfs-ls.sh b/test/sharness/t0200-unixfs-ls.sh
index f5dc20d305458edc0a667c8733fb22ae7be58b63..4d980e44e468d325ea06c766451d5cf95446e077 100755
--- a/test/sharness/t0200-unixfs-ls.sh
+++ b/test/sharness/t0200-unixfs-ls.sh
@@ -57,10 +57,21 @@ test_ls_cmd() {
 			QmSix55yz8CzWXf5ZVM9vgEvijnEeeXiTSarVtsqiiCJss:
 			128
 			a
-
 		EOF
 		test_cmp expected_ls actual_ls
 	'
+
+	test_expect_success "'ipfs unixfs ls <file hashes>' succeeds" '
+		ipfs unixfs ls /ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024 QmQNd6ubRXaNG6Prov8o6vk3bn6eWsj9FxLGrAVDUAGkGe >actual_ls_file
+	'
+
+	test_expect_success "'ipfs unixfs ls <file hashes>' output looks good" '
+		cat <<-\EOF >expected_ls_file &&
+			/ipfs/QmR3jhV4XpxxPjPT3Y8vNnWvWNvakdcT3H6vqpRBsX1MLy/1024
+			QmQNd6ubRXaNG6Prov8o6vk3bn6eWsj9FxLGrAVDUAGkGe
+		EOF
+		test_cmp expected_ls_file actual_ls_file
+	'
 }