The Missing Save Button That Wasn't a Permissions Problem
A MediaWiki Save button vanished after a PHP 8 upgrade with no error in sight. The real cause wasn't the PHP version — it was one broken line in EditPage.php.
- MediaWiki
- PHP
- Debugging
The report
A colleague pinged me: she could open any page on our internal MediaWiki wiki, edit the text, fill in the summary — but the Save button was gone. Just a “Cancel” link sitting where the buttons used to be. Her first guess was the obvious one: “looks like I don’t have access anymore.”
That’s the trap. When something you could do yesterday silently stops working, “it must be permissions” is the story your brain reaches for first. It was wrong here — and the way it was wrong is the whole point of this post.
Permissions was the first thing I ruled out
MediaWiki has a tell for this. If you genuinely can’t edit a page, it shows you a read-only “View source” box — no summary field, no “minor edit” checkbox, nothing to submit. She had all of those. The edit form itself was fully live. So she had edit rights; the button simply wasn’t being drawn. That ruled out an entire category of causes in about ten seconds, just by knowing what the actual permission-denied state looks like instead of assuming.
The rabbit hole: blaming PHP
The wiki ran an ancient MediaWiki 1.29 (from 2017), and the host had quietly upgraded the server’s PHP version to 8.2 in the background. Old software choking on a newer PHP runtime is a classic, well-worn failure story, so I chased it first: got hosting access, pinned that site back down to PHP 7.4.
The button was still gone.
That’s when it got interesting — because there was no error anywhere to chase. Clean
PHP logs. Clean browser console. Empty Common.js. Nothing crashed. Everything on the
page rendered correctly except three specific buttons.
”No error” is a clue, not a comfort
A silent failure that looks like a permissions issue almost always means some code ran, produced nothing, and moved on without complaining about it. So I stopped guessing at platform-level causes and went straight to the source that builds the edit form. One line was the whole bug:
$wgOut->addHTML( "\n", implode( $this->getEditButtons( $tabindex ) ) . "\n" );
addHTML() takes exactly one argument. Someone had written a comma where there should
have been a string concatenation . — so the fully-built buttons string silently
became a second argument that addHTML() never looked at and quietly discarded. The
buttons were being generated perfectly, every time, and then dropped on the floor
before they ever reached the page. No crash, no warning, no log line — just three
buttons that never existed in the output.
The kicker
Diffing against an older backup of the file showed the original, correct line. Someone
had touched this core file during the PHP 8 migration — the original code used a
deprecated implode() argument order that PHP 8 actually removed, so it needed a real
fix — and while “fixing” that, introduced this typo. The PHP-version story wasn’t
wrong, exactly; it’s what caused someone to edit the file. But the bug that shipped
wasn’t a PHP compatibility issue at all. It was a one-character mistake wearing a
platform problem’s clothes.
One line changed — comma to dot — and the Save button was back.
What I took from it
- “It looks like a permissions issue” is a hypothesis, not a diagnosis. Check what the software actually shows a user with no permission. It almost always looks different from a bug that merely resembles one, and that check takes seconds.
- A missing error is information, not an absence of one. If nothing is logging and nothing is crashing, the bug is very likely a silent no-op somewhere in the render path — which means the fix is in the source, not the logs.
- Keep known-good backups of code you didn’t write yourself. A five-second diff
against an old copy of
EditPage.phppointed straight at the one changed line and turned a guessing game into a certainty.
Hours of PHP-version archaeology, and the actual fix was deleting one comma. That’s debugging: most of the work is refusing to trust the first plausible story.