diff --git a/lib/src/io.dart b/lib/src/io.dart
index 0af650bda17e36943bffb7cebc344503f6bb4bbb..a7bbb3aba34cf10b82efd667b8c0be542bdbdabf 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -33,7 +33,7 @@ bool entryExists(String path) =>
 
 /// Returns whether [link] exists on the file system. This will return `true`
 /// for any symlink, regardless of what it points at or whether it's broken.
-bool linkExists(String path) => new Link(path).existsSync();
+bool linkExists(String link) => new Link(link).existsSync();
 
 /// Returns whether [file] exists on the file system. This will return `true`
 /// for a symlink only if that symlink is unbroken and points to a file.
@@ -146,7 +146,15 @@ List<String> listDir(String dir, {bool recursive: false,
     log.io("Listing directory $dir.");
 
     var children = [];
-    for (var entity in new Directory(dir).listSync()) {
+    for (var entity in new Directory(dir).listSync(followLinks: false)) {
+      // TODO(nweiz): remove this when issue 4928 is fixed.
+      if (entity is Link) {
+        var link = entity.path;
+        // We treat broken symlinks as files, in that we don't want to recurse
+        // into them.
+        entity = dirExists(link) ? new Directory(link) : new File(link);
+      }
+
       if (entity is File) {
         var file = entity.path;
         if (!includeHiddenFiles && path.basename(file).startsWith('.')) {
diff --git a/test/io_test.dart b/test/io_test.dart
index cc8eb9dbc56b04e2e0c6ec65f71d7798fac5bcde..cd276bc4241b772a0fceec94ee0dffbd68b3b0f0 100644
--- a/test/io_test.dart
+++ b/test/io_test.dart
@@ -119,6 +119,20 @@ main() {
         ]));
       }), completes);
     });
+
+    test('treats a broken symlink as a file', () {
+      expect(withTempDir((temp) {
+        writeTextFile(path.join(temp, 'file1.txt'), '');
+        createDir(path.join(temp, 'dir'));
+        createSymlink(path.join(temp, 'dir'), path.join(temp, 'linkdir'));
+        deleteEntry(path.join(temp, 'dir'));
+
+        expect(listDir(temp, recursive: true), unorderedEquals([
+          path.join(temp, 'file1.txt'),
+          path.join(temp, 'linkdir')
+        ]));
+      }), completes);
+    });
   });
 
   testExistencePredicate("entryExists", entryExists,