diff --git a/novaclient/base.py b/novaclient/base.py
index b2c9a84d5..a9e5b3cfe 100644
--- a/novaclient/base.py
+++ b/novaclient/base.py
@@ -19,6 +19,8 @@
 Base utilities to build API operation managers and objects on top of.
 """
 
+import contextlib
+import os
 from novaclient import exceptions
 
 
@@ -73,7 +75,40 @@ class Manager(object):
         #           unlike other services which just return the list...
         if type(data) is dict:
             data = data['values']
-        return [obj_class(self, res, loaded=True) for res in data if res]
+
+        with self.uuid_cache(obj_class, mode="w"):
+            return [obj_class(self, res, loaded=True) for res in data if res]
+
+    @contextlib.contextmanager
+    def uuid_cache(self, obj_class, mode):
+        """
+        Cache UUIDs for bash autocompletion.
+
+        The UUID cache works by checking to see whether an ID is UUID-like when
+        we create a resource object (e.g. a Image or a Server), and if it is,
+        we add it to a local cache file.  We maintain one cache file per
+        resource type so that we can refresh them independently.
+
+        A resource listing will clear and repopulate the UUID cache.
+
+        A resource create will append to the UUID cache.
+
+        Delete is not handled because listings are assumed to be performed
+        often enough to keep the UUID cache reasonably up-to-date.
+        """
+        resource = obj_class.__name__.lower()
+        filename = os.path.expanduser("~/.novaclient_cached_%s_uuids" %
+                                      resource)
+        self._uuid_cache = open(filename, mode)
+        try:
+            yield
+        finally:
+            self._uuid_cache.close()
+            del self._uuid_cache
+
+    def write_uuid_to_cache(self, uuid):
+        if hasattr(self, '_uuid_cache'):
+            self._uuid_cache.write("%s\n" % uuid)
 
     def _get(self, url, response_key):
         resp, body = self.api.client.get(url)
@@ -83,7 +118,9 @@ class Manager(object):
         resp, body = self.api.client.post(url, body=body)
         if return_raw:
             return body[response_key]
-        return self.resource_class(self, body[response_key])
+
+        with self.uuid_cache(self.resource_class, mode="a"):
+            return self.resource_class(self, body[response_key])
 
     def _delete(self, url):
         resp, body = self.api.client.delete(url)
@@ -215,6 +252,12 @@ class Resource(object):
         self._add_details(info)
         self._loaded = loaded
 
+        # NOTE(sirp): ensure `id` is already present because if it isn't we'll
+        # enter an infinite loop of __getattr__ -> get -> __init__ ->
+        # __getattr__ -> ...
+        if 'id' in self.__dict__ and len(str(self.id)) == 36:
+            self.manager.write_uuid_to_cache(self.id)
+
     def _add_details(self, info):
         for (k, v) in info.iteritems():
             setattr(self, k, v)
diff --git a/tools/nova.bash_completion b/tools/nova.bash_completion
index ab1579fc0..0bcd0afdf 100644
--- a/tools/nova.bash_completion
+++ b/tools/nova.bash_completion
@@ -13,6 +13,10 @@ _nova()
           zone zone-add zone-boot zone-delete zone-info zone-list help
           --debug --endpoint_name --password --projectid --region_name --url
           --username --version"
+
+    UUID_CACHE=~/.novaclient_cached_*_uuids
+    opts+=" "$(cat $UUID_CACHE 2> /dev/null | tr '\n' ' ')
+
     COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
 }
 complete -F _nova nova