diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -25,6 +25,7 @@
     'AphrontDatabaseTransactionState' => 'aphront/storage/connection/AphrontDatabaseTransactionState.php',
     'AphrontDeadlockQueryException' => 'aphront/storage/exception/AphrontDeadlockQueryException.php',
     'AphrontDuplicateKeyQueryException' => 'aphront/storage/exception/AphrontDuplicateKeyQueryException.php',
+    'AphrontInvalidCredentialsQueryException' => 'aphront/storage/exception/AphrontInvalidCredentialsQueryException.php',
     'AphrontIsolatedDatabaseConnection' => 'aphront/storage/connection/AphrontIsolatedDatabaseConnection.php',
     'AphrontLockTimeoutQueryException' => 'aphront/storage/exception/AphrontLockTimeoutQueryException.php',
     'AphrontMySQLDatabaseConnection' => 'aphront/storage/connection/mysql/AphrontMySQLDatabaseConnection.php',
@@ -529,7 +530,7 @@
     'AASTTree' => 'Phobject',
     'AbstractDirectedGraph' => 'Phobject',
     'AbstractDirectedGraphTestCase' => 'PhutilTestCase',
-    'AphrontAccessDeniedQueryException' => 'AphrontRecoverableQueryException',
+    'AphrontAccessDeniedQueryException' => 'AphrontQueryException',
     'AphrontBaseMySQLDatabaseConnection' => 'AphrontDatabaseConnection',
     'AphrontCharacterSetQueryException' => 'AphrontQueryException',
     'AphrontConnectionLostQueryException' => 'AphrontRecoverableQueryException',
@@ -542,6 +543,7 @@
     'AphrontDatabaseTransactionState' => 'Phobject',
     'AphrontDeadlockQueryException' => 'AphrontRecoverableQueryException',
     'AphrontDuplicateKeyQueryException' => 'AphrontQueryException',
+    'AphrontInvalidCredentialsQueryException' => 'AphrontQueryException',
     'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection',
     'AphrontLockTimeoutQueryException' => 'AphrontRecoverableQueryException',
     'AphrontMySQLDatabaseConnection' => 'AphrontBaseMySQLDatabaseConnection',
diff --git a/src/aphront/storage/connection/mysql/AphrontBaseMySQLDatabaseConnection.php b/src/aphront/storage/connection/mysql/AphrontBaseMySQLDatabaseConnection.php
--- a/src/aphront/storage/connection/mysql/AphrontBaseMySQLDatabaseConnection.php
+++ b/src/aphront/storage/connection/mysql/AphrontBaseMySQLDatabaseConnection.php
@@ -275,42 +275,69 @@
     $this->throwQueryCodeException($errno, $error);
   }
 
-  protected function throwQueryCodeException($errno, $error) {
-    $exmsg = "#{$errno}: {$error}";
+  private function throwCommonException($errno, $error) {
+    $message = pht('#%d: %s', $errno, $error);
 
     switch ($errno) {
       case 2013: // Connection Dropped
-        throw new AphrontConnectionLostQueryException($exmsg);
+        throw new AphrontConnectionLostQueryException($message);
       case 2006: // Gone Away
         $more = pht(
           "This error may occur if your MySQL '%s' or '%s' ".
           "configuration values are set too low.",
           'wait_timeout',
           'max_allowed_packet');
-        throw new AphrontConnectionLostQueryException("{$exmsg}\n\n{$more}");
+        throw new AphrontConnectionLostQueryException("{$message}\n\n{$more}");
       case 1213: // Deadlock
-        throw new AphrontDeadlockQueryException($exmsg);
+        throw new AphrontDeadlockQueryException($message);
       case 1205: // Lock wait timeout exceeded
-        throw new AphrontLockTimeoutQueryException($exmsg);
+        throw new AphrontLockTimeoutQueryException($message);
       case 1062: // Duplicate Key
         // NOTE: In some versions of MySQL we get a key name back here, but
         // older versions just give us a key index ("key 2") so it's not
         // portable to parse the key out of the error and attach it to the
         // exception.
-        throw new AphrontDuplicateKeyQueryException($exmsg);
+        throw new AphrontDuplicateKeyQueryException($message);
       case 1044: // Access denied to database
-      case 1045: // Access denied (auth)
       case 1142: // Access denied to table
       case 1143: // Access denied to column
-        throw new AphrontAccessDeniedQueryException($exmsg);
+        throw new AphrontAccessDeniedQueryException($message);
+      case 1045: // Access denied (auth)
+        throw new AphrontInvalidCredentialsQueryException($message);
       case 1146: // No such table
       case 1049: // No such database
       case 1054: // Unknown column "..." in field list
-        throw new AphrontSchemaQueryException($exmsg);
-      default:
-        // TODO: 1064 is syntax error, and quite terrible in production.
-        throw new AphrontQueryException($exmsg);
+        throw new AphrontSchemaQueryException($message);
     }
+
+    // TODO: 1064 is syntax error, and quite terrible in production.
+
+    return null;
+  }
+
+  protected function throwConnectionException($errno, $error, $user, $host) {
+    $this->throwCommonException($errno, $error);
+
+    $message = pht(
+      'Attempt to connect to %s@%s failed with error #%d: %s.',
+      $user,
+      $host,
+      $errno,
+      $error);
+
+    throw new AphrontConnectionQueryException($message, $errno);
+  }
+
+
+  protected function throwQueryCodeException($errno, $error) {
+    $this->throwCommonException($errno, $error);
+
+    $message = pht(
+      '#%d: %s',
+      $errno,
+      $error);
+
+    throw new AphrontQueryException($message, $errno);
   }
 
   /**
diff --git a/src/aphront/storage/connection/mysql/AphrontMySQLDatabaseConnection.php b/src/aphront/storage/connection/mysql/AphrontMySQLDatabaseConnection.php
--- a/src/aphront/storage/connection/mysql/AphrontMySQLDatabaseConnection.php
+++ b/src/aphront/storage/connection/mysql/AphrontMySQLDatabaseConnection.php
@@ -61,14 +61,7 @@
     if (!$conn) {
       $errno = mysql_errno();
       $error = mysql_error();
-      throw new AphrontConnectionQueryException(
-        pht(
-          'Attempt to connect to %s@%s failed with error #%d: %s.',
-          $user,
-          $host,
-          $errno,
-          $error),
-        $errno);
+      $this->throwConnectionException($errno, $error, $user, $host);
     }
 
     if ($database !== null) {
diff --git a/src/aphront/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php b/src/aphront/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php
--- a/src/aphront/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php
+++ b/src/aphront/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php
@@ -69,14 +69,7 @@
     $errno = $conn->connect_errno;
     if ($errno) {
       $error = $conn->connect_error;
-      throw new AphrontConnectionQueryException(
-        pht(
-          'Attempt to connect to %s@%s failed with error #%d: %s.',
-          $user,
-          $host,
-          $errno,
-          $error),
-        $errno);
+      $this->throwConnectionException($errno, $error, $user, $host);
     }
 
     $ok = @$conn->set_charset('utf8mb4');
diff --git a/src/aphront/storage/exception/AphrontAccessDeniedQueryException.php b/src/aphront/storage/exception/AphrontAccessDeniedQueryException.php
--- a/src/aphront/storage/exception/AphrontAccessDeniedQueryException.php
+++ b/src/aphront/storage/exception/AphrontAccessDeniedQueryException.php
@@ -1,4 +1,4 @@
 <?php
 
 final class AphrontAccessDeniedQueryException
-  extends AphrontRecoverableQueryException {}
+  extends AphrontQueryException {}
diff --git a/src/aphront/storage/exception/AphrontInvalidCredentialsQueryException.php b/src/aphront/storage/exception/AphrontInvalidCredentialsQueryException.php
new file mode 100644
--- /dev/null
+++ b/src/aphront/storage/exception/AphrontInvalidCredentialsQueryException.php
@@ -0,0 +1,4 @@
+<?php
+
+final class AphrontInvalidCredentialsQueryException
+  extends AphrontQueryException {}