When Your Database Client Lies to You: Patching Workbench 26 for MariaDB
I run four database servers from one Windows machine: two MySQL 8.4 instances, one local install, and a MariaDB 10.11 box behind CyberPanel on my LAN. Three of them opened fine in the shiny new MySQL Workbench 26.7.0. One didn’t.
The error it gave me was this:
TypeError: DbModuleSession.on_session_message() missing 1 required positional argument: 'result'
That message contains no information about databases, networking, credentials, or MariaDB. It’s a Python argument-count error inside the client. And it was the single most misleading error message I’ve chased this year — because it wasn’t the bug. It was the error reporter crashing while trying to report the bug.
This post is the trail from that message to three patches in Oracle’s own shipping code, and the underlying design assumption that causes all three.
Why this matters
If you keep more than one database engine around — MySQL here, MariaDB there — you’ll hit this class of failure eventually, and the error text will actively mislead you. Three things in this post are worth knowing even if you never open Workbench:
- A garbage error message is itself a finding. When a tool reports something structurally impossible, suspect its error path before its logic.
major >= 8is not a reliable “is this MySQL?” test. MariaDB reports version 10.x and 11.x. A lot of software gets this wrong.- The version-number collision is systemic, not a one-off bug in one app — it’s why the same fix had to be applied three times in three files.
The setup
The four connections, and which one broke:
| Server | Engine | Version | Workbench 26.7.0 |
|---|---|---|---|
| DSM NAS | MySQL | 8.4 | works |
| unRaid | MySQL | 8.4 | works |
| localhost (since removed) | MySQL | 8.4.6 | works |
| CyberPanel VM | MariaDB | 10.11.19 | fails |
The pattern is obvious in hindsight: everything MySQL 8.4 works, the one MariaDB doesn’t. But “it’s the odd one out” is not a diagnosis, and I didn’t trust it yet.
What I tried first, and why it was wrong
Theory 1: TLS. The connection profile had useSSL = 1 with empty sslCA, sslCert, sslKey. For MySQL 8.x that’s fine — it auto-generates certificates. For MariaDB, maybe not.
This one turned out to be half right, and I’ll come back to it, because it’s a genuine second fault that produces a different error.
Theory 2: credentials or grants. Also wrong, and I could disprove it cheaply. Workbench 26 ships its own MySQL Shell binary. Running that binary by hand against the same host is a direct test of everything except Workbench’s own code:
MSH="/c/Program Files/MySQL/MySQL Workbench/resources/app/shell/bin/mysqlsh.exe"
"$MSH" --sql --uri "admin:[email protected]:3306" \
-e "select current_user(), version();"
current_user() version()
admin@% 10.11.19-MariaDB-ubu2404
It connects. Same binary, same host, same credentials — works from the command line, fails from the GUI. That narrows it decisively: the network, server, grants, and password are all fine. Whatever is broken lives in Workbench’s own session-handling code.
If you take one technique from this post, take that one. Before debugging a client, drive the client’s own underlying tool directly to split “the thing is broken” from “this UI is broken.”
Getting the real error out of it
Workbench 26 stores its logs somewhere new. The useful ones:
%APPDATA%\MySQL Workbench\MySQL Workbench-electron.log # app-level, session stderr
%APPDATA%\MySQL\mysqlsh\mysqlsh.log # per-connect log
The connect log showed something odd. The session connected, then stopped:
Info: Connecting to MySQL at: [email protected]:3306
Info: main: tid=0: CONNECTED: 192.168.1.124:3306
Warning: Could not enable session tracking for sql_mode: Session tracker not supported by server
CONNECTED, and then nothing. No error, no disconnect. The connection succeeded and the session died silently afterwards.
Meanwhile the Electron log had the TypeError from the top of this post, repeated fifty-one times. That repetition is the clue: an error handler being called once per failure, crashing every time.
The bug was in the error handler
From gui_plugin\core\dbms\DbSession.py:
def terminate_thread(self):
self._close_database(True)
if self.thread_error is not None:
logger.error(f"Thread {self._id} exiting with code {self.thread_error}")
self._message_callback("ERROR", self.thread_error) # two arguments
self._term_complete.set()
And the callback it’s calling, in DbModuleSession.py:
def on_session_message(self, type, message, result, request_id=None):
Three required parameters. The call passes two. So whenever a session thread dies, the code that reports it dies too — and the TypeError replaces the original error on its way to the user.
That’s the whole reason this bug was hard to see. Every real error in that session was being swallowed by a crash in the reporting path. Fixing the handler doesn’t fix the underlying problem; it makes the problem visible. Which is what you need.
The correct call shape is right there in a sibling file, DbMySQLSession.py:
self._message_callback(
"PENDING",
"Connection lost, reconnecting session...",
None,
self._current_task_id,
)
So the fix is to match it:
self._message_callback(
"ERROR", str(self.thread_error), None, self._current_task_id
)
Restart, retry, and the error message finally tells the truth:
MySQL Error 1193 (HY000): Unknown system variable 'gtid_mode'
Now we’re somewhere. That’s a real error, and it names a real cause.
The actual root cause
gtid_mode is a MySQL-only system variable. MariaDB doesn’t have it — it has gtid_strict_mode, gtid_binlog_pos, gtid_current_pos, and others, but nothing named gtid_mode.
You can confirm both halves in one minute:
# On MariaDB — no gtid_mode anywhere in the list
"$MSH" --sql --uri "..." -e "show variables like 'gtid%';"
# On MySQL 8.4 — there it is
"$MSH" --sql --uri "..." -e "select @@gtid_mode;"
The offending code, in gui_plugin\migration\lib\backend\replication.py:
if log_bin:
if session.nversion >= 56000:
gtid_mode = session.run_sql("select @@gtid_mode").fetch_one()[0]
session.nversion for MariaDB 10.11.19 is 101119. That’s greater than 56000, so the guard passes, so the query runs, so MariaDB rejects it and the session dies.
And here is the design flaw underneath it. The guard assumes a version number can tell you which engine you’re talking to. It can’t. MariaDB and MySQL share a single version register:
| Engine | Reports as |
|---|---|
| MySQL 5.7 | 5.7.x |
| MySQL 8.0 / 8.4 | 8.0.x / 8.4.x |
| MariaDB 10.6 | 10.6.x |
| MariaDB 10.11 | 10.11.x |
| MariaDB 11.x | 11.x |
Every MariaDB version is numerically above every MySQL version in production use. So nversion >= 56000 is true for MariaDB. So is >= 80000. Any feature-gate written as a MySQL version comparison will pass against MariaDB and then send it syntax it doesn’t understand.
The reliable discriminator is @@version_comment, which returns mariadb.org binary distribution on MariaDB and something else on MySQL. Workbench even fetches that value — it stores it as VERSION_COMMENT right next to VERSION_INFO — and then never uses it to make this decision.
This is not sloppiness in one place. It’s a wrong assumption applied consistently, which is why the same bug appeared three separate times.
Three patches, same mistake
Once I knew what to look for, the rest were mechanical. All three are the same fix: make the MySQL-only probe tolerant.
Patch 1 — replication.py
if session.nversion >= 56000:
try:
gtid_mode = session.run_sql("select @@gtid_mode").fetch_one()[0]
except Exception:
# MariaDB has no gtid_mode system variable; ignore and continue.
gtid_mode = None
Worth noting: the same query is called elsewhere in the codebase, in checks.py, and there it’s already wrapped in try/except. Oracle knew this call could fail on some servers. They just didn’t apply that knowledge here. Only triggers when binlog is enabled, which it is on this box.
Patch 2 — DbSession.py
The error handler from earlier. Strictly this fixes reporting, not MariaDB compatibility — but it’s the patch that made patches 1 and 3 findable. Do it early.
Patch 3 — DbMySQLSessionSetupTasks.py
Next failure, surfaced by patch 2:
MySQL Error 1193: Unknown system variable 'explain_json_format_version'
Which lives here, in a session-setup task that runs on every connect:
version = self.get_data(common.MySQLData.VERSION_INFO)
major, minor, _ = version.split(".", 2)
if int(major) < 8 or int(major) == 8 and int(minor) < 3:
return
self.execute("SET SESSION explain_json_format_version=2")
@@version is 10.11.19-MariaDB-ubu2404. Split it: major = 10, minor = 11. Is int(major) < 8? No. Guard passes. SET SESSION explain_json_format_version=2 is MySQL-only. Error 1193 again.
Same fix:
try:
self.execute("SET SESSION explain_json_format_version=2")
except Exception:
pass
The separate TLS fault
Remember Theory 1? It was a real problem, just a different one — and it fails before authentication, which makes it look like a credential issue.
Your connection profile can hold useSSL = 1 with no certificates. Against MySQL 8.x that’s harmless. Against MariaDB with TLS switched off, the handshake is refused outright:
"$MSH" --sql --uri "..." -e "select @@have_ssl;"
# have_ssl: DISABLED
"$MSH" --sql --uri "...?ssl-mode=REQUIRED" -e "select 1;"
# ERROR 2026 (HY000): SSL connection error:
# SSL is required but the server doesn't support it
The fix is one value in %APPDATA%\MySQL\Workbench\connections.xml — set useSSL to 0 for that connection only. On a trusted LAN, a plain connection is fine. Back the file up first.
Two independent faults, two different error signatures. Worth separating in your head: TLS failures happen before login; the 1193 failures happen after. If the server rejects your handshake, credentials never get evaluated.
How to patch files under Program Files
The Workbench files are shipped read-only, and this bit me twice in a way worth documenting: writes fail silently. PowerShell’s WriteAllText reported success while the file on disk was unchanged — same size, same timestamp. A .Replace() on the content reported success and did nothing, because of line-ending differences.
What actually works:
- Back up in place —
Copy-Item $f "$f.bak", so the pristine original sits next to your version. - Clear the read-only flag and confirm it:
attrib -R, then check(Get-Item $f).Attributesno longer saysReadOnly. - Patch by line index, not string replacement — read all lines, splice, write all lines. Immune to CRLF and quoting differences.
- Restore read-only afterwards with
attrib +R. - Delete the stale
__pycache__.pyc— otherwise Python keeps running the old compiled module and you’ll conclude your patch didn’t work. This one is easy to forget and produces a genuinely confusing result. - Verify syntax before relaunching:
ast.parse()on the file, ornode --checkfor JS.
Then restart the app so the patched source is loaded.
Test the patch before you hand it back
A patched build that hasn’t been exercised is a guess. Since the failure depends on server behaviour, simulate the server rather than clicking hopefully at the GUI:
class FakeSession:
nversion = 101119 # MariaDB 10.11.19
def run_sql(self, q):
if "gtid_mode" in q:
raise Exception("1193 (HY000): Unknown system variable 'gtid_mode'")
if "@@log_bin, @@binlog_format" in q:
return FakeResult((1, "ROW")) # binlog ON triggers the path
Load the patched function with exec(), call it with that fake, and assert it returns instead of raising. It’s a five-minute test that turns “I think this works” into “this path is covered.” It also confirmed the patch was genuinely being loaded, before I blamed the app.
The result
All four connections work from Workbench 26.7.0 now, including the MariaDB box. Three patched files in Oracle’s shipping code, each with a .bak beside it:
| File | Change |
|---|---|
replication.py | guard select @@gtid_mode |
DbSession.py | fix error-handler argument count |
DbMySQLSessionSetupTasks.py | guard SET SESSION explain_json_format_version |
One thing I’d flag rather than dress up: these are patches to a closed application’s bundled code, not a contribution upstream. They’ll be overwritten by the next Workbench update, and the fault list may be longer than three — I stopped when the connections worked. If it breaks again after upgrading, expect to walk the same trail and possibly find a fourth instance of the same mistake.
What I’d do differently
- Drive the client’s own binary first.
mysqlshfrom Workbench’s own install folder settled the “is it the server?” question in one command. I should have started there instead of theorising about TLS. - Treat an impossible error message as evidence.
on_session_message() missing 1 required positional argumentis not a database error. When the text doesn’t match the domain, suspect the error path itself — and fix that first, because it’s blocking your view of everything else. - Read the sibling files. Both the
gtid_modeguard and the correct_message_callbacksignature existed correctly elsewhere in the same codebase. The bug wasn’t ignorance of the right pattern; it was inconsistent application of it. - Notice version-gate arithmetic as a smell. Any
if version >= Nbranch that decides which database engine you’re facing deserves suspicion. Ask what MariaDB reports, not what MySQL reports. - Keep the old client installed. Workbench 8.0.x has no such probe and connects to MariaDB without complaint. Running 26.x for MySQL 8.4 and 8.0.x for MariaDB is a legitimate strategy — and it’s the reason this was an evening’s puzzle rather than a blocked workday.
Version numbers are the easiest thing to compare and the easiest thing to compare wrongly. Two projects sharing a numbering scheme is not the same as two projects sharing a feature set.