phacility/phabricator · critical · Exception
Attempting to issue a write query on a read-only connection
Error message
Attempting to issue a write query on a read-only connection (to database "%s")!
What it means
Thrown by AphrontBaseMySQLDatabaseConnection::checkWrite() when a statement classified as a write (anything not starting with SELECT/SHOW/EXPLAIN, after optional leading parens) is issued on a connection opened in read-only mode. Phabricator marks replica connections read-only; this guard makes accidental writes to a replica loud and immediate instead of silently corrupting data. On writes to writable connections it also fires AphrontWriteGuard::willWrite() for write-safety checking.
Source
Thrown at src/infrastructure/storage/connection/mysql/AphrontBaseMySQLDatabaseConnection.php:299
} else if (is_bool($result)) {
return $this->getAffectedRows();
}
$rows = array();
while (($row = $this->fetchAssoc($result))) {
$rows[] = $row;
}
$this->freeResult($result);
return $rows;
}
protected function checkWrite($raw_query) {
// NOTE: The opening "(" allows queries in the form of:
//
// (SELECT ...) UNION (SELECT ...)
$is_write = !preg_match('/^[(]*(SELECT|SHOW|EXPLAIN)\s/', $raw_query);
if ($is_write) {
if ($this->getReadOnly()) {
throw new Exception(
pht(
'Attempting to issue a write query on a read-only '.
'connection (to database "%s")!',
$this->getConfiguration('database')));
}
AphrontWriteGuard::willWrite();
return true;
}
return false;
}
protected function throwQueryException($connection) {
if ($this->nextError) {
$errno = $this->nextError;
$error = pht('Simulated error.');
$this->nextError = null;
} else {View on GitHub (pinned to 5720a38cfe)
Solutions
- Route the write to the master/writable connection (open a new connection without read-only, or fix the cluster role configuration)
- Check bin/config get cluster.databases / partition configuration: exactly one host must be writable
- In application code, never save Lisk objects on connections used for replica reads — re-load/save via the default connection
- During/after a masterswitch, let connections reconnect so roles refresh
Example fix
// before: writing on whatever connection is at hand
$conn = $this->getAnyDatabaseConnection();
queryfx($conn, 'UPDATE %T SET status = %s WHERE id = %d', ...);
// after: writes go to the writable master connection
$conn_w = id(new PhabricatorUser())->establishConnection('w');
queryfx($conn_w, 'UPDATE %T SET status = %s WHERE id = %d', ...); Defensive patterns
Strategy: validation
Validate before calling
// Before writing, confirm the connection is writable.
if ($conn->getReadOnly()) {
$conn = $lisk_object->establishConnection('w'); // master connection
}
queryfx($conn, 'UPDATE ...'); Try / catch
try {
queryfx($conn, $write_sql);
} catch (Exception $ex) {
if (preg_match('/read-only connection/', $ex->getMessage())) {
$conn = $lisk_object->establishConnection('w');
queryfx($conn, $write_sql); // retry once on the writable connection
} else {
throw $ex;
}
} Prevention
- Use Lisk's establishConnection('w') for writes and 'r' only for reads — never cache a replica connection for reuse as a writer
- Keep cluster.databases roles correct: exactly one writable master at a time
- Treat this exception as a configuration bug to fix, not a transient error to retry
When it happens
Trigger: Issuing execute() with INSERT/UPDATE/DELETE/CREATE through a connection obtained from a replica (getManagementConnectionData / partition or cluster configuration giving a read-only connection); daemons or lisk objects that lazily write while iterating objects loaded from a read-only connection; a host in 'replica' role receiving writes during a cluster masterswitch.
Common situations: Misconfigured cluster.databases marking the primary as a replica; code paths reading from a replica and then saving a Lisk object on the same connection; during masterswitch/failover the writable role moves while a request still holds an old read-only connection.
Related errors
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/a06374df40cc1468.
Report an issue: GitHub.