I filed a critical bug against my own tool. Then I read the code - and my own root cause was wrong.
Three days ago I filed the most serious issue my project has ever had - against myself. The tool is safari-mcp, an MCP server that lets an AI agent drive the Safari you're already logged into. That premise is the whole value proposition, and it's also the whole danger: the user is using this browser at the same time as the agent.
The single promise the project makes is "the agent never touches a tab it didn't open." The issue was that the promise had broken. Two of a user's tabs ended up displaying pages the agent had loaded. Nothing was closed - the back-history survived - but scroll position, in-page state, anything unsaved: gone.
I wrote up the incident, traced the root cause, and proposed a fix. Then today I opened the file to actually write that fix. My root cause was wrong. The fix I proposed had already shipped, three months ago. And the real bug was sitting four lines below it, wearing the word "fix" in its own log message.
What I claimed
Here's what I wrote in the issue: _ownedTabs (and every tabIndex parameter) is a positional handle to a mutable, user-controlled list. Positional handles are only valid as long as nobody else mutates the list.
And the proposed fix: Track ownership by a stable identity, not a position. On safari_new_tab, inject a sentinel into the page (e.g. window.__safariMcpTabId = "<uuid>"), and resolve tabIndex โ real tab by scanning windows for the matching sentinel.
Confident. Specific. Reasonable. I even left a follow-up comment calling the sentinel design "the plan of record." I wrote all of that from my memory of the architecture. I did not open safari.js.
What was actually in the file
The sentinel already existed. It had been there since v2.8.3, released April 14 - a release literally titled "bulletproof tab tracking via window.__mcpTabMarker."
// ========== TAB IDENTITY MARKER ==========
// - window.name : survives EVERY navigation (full loads, redirects,
// cross-origin). The browser preserves window.name by
// design - the bulletproof identity that index/URL lack.
// - window.__mcpTabMarker : survives SPA / same-document routing (secondary marker).
I had written the fix I was now proposing. I'd written the comment explaining why it was the right fix. I'd forgotten I'd done it.
So the interesting question stopped being "why is ownership positional" - it isn't - and became "if identity resolution is already there, how did a user's tab still get navigated?"
Three exits, one of them guesses
resolveActiveTab() is the function that answers "which tab is ours, right now?" It has a strategy ladder, and what matters is how each rung fails.
Rung 1 - the marker scan. Loop every tab, ask each one whether window.name matches our marker. Found it? That's our tab, whatever index it's sitting at. This is identity, and it's correct.
Rung 2 - no URL to fall back on:
if (_st().hasOwnedTab && !_st().activeTabMarker) {
_st().activeTabIndex = null;
}
Identity lost โ drop the index. Fails closed. โ
Rung 3 - the URL scan comes back empty:
if (_st().hasOwnedTab && !_st().activeTabMarker) {
console.error('[Safari MCP] Tab identity lost (marker + URL unresolved) - clearing index to avoid targeting the user\'s tab');
_st().activeTabIndex = null;
return null;
}
Same call, same instinct. Fails closed. โ
And then, four lines later, in the same block:
if (_st().activeTabIndex && _st().activeTabIndex > tabCount) {
console.error(`[Safari MCP] Tab ghost proactive fix: index ${_st().activeTabIndex} > tabCount ${tabCount}, clamping to ${tabCount}`);
_st().activeTabIndex = tabCount;
}
Read that carefully, because I didn't for three months. We tracked tab 8. The window now has 7 tabs - because the user closed one, or tore one into its own window. Our index is out of range. That is the exact moment the code has learned it no longer knows where our tab is.
And it responds by clamping the index to tabCount: tab 7. The last tab in the window. A tab we have never seen, that belongs to the user, chosen for no reason other than that it's the highest index that won't throw. Then it logs the words "proactive fix."
That's the bug. Not positional ownership - a fail-open sitting between two fail-closed branches, in the safety-critical path, describing itself as a fix.
How it got there
git log -S puts the clamp at March 31. The identity marker landed April 14. The clamp is two weeks older than the mechanism that made it obsolete. It's from the era when the index was genuinely all we had, when "out of range" produced ugly AppleScript errors and clamping made them stop.
And that's the actual lesson, the one that generalizes past my weird little macOS project: When you add a better mechanism, the old heuristic does not remove itself.
The v2.8.3 work added identity resolution and correctly rewired the branches it was looking at - the two hasOwnedTab && !activeTabMarker guards are the new thinking, and they fail closed because in April I understood the stakes. The clamp wasn't rewired, because it didn't look like an ownership decision. It looked like input validation. It looked like the careful line. It had a bounds check and an error log.
Every upgrade leaves fossils like this. The dangerous ones aren't the code that looks scary. They're the code that looks like it's on your side.
"Proactive fix" is a confession
Here's the tell I want to hand you, because it's cheap and it's reusable. The clamp was written to fix a symptom: an index pointing past the end of the array. It makes that symptom disappear by inventing a plausible value. tabCount isn't a computed answer to "where is our tab" - it's the nearest number that doesn't crash.
Any code that converts "I don't know" into a plausible value is a fail-open wearing a fix's clothes. Once you have that phrasing, you start seeing them everywhere:
?? 0on a total that should have been fetched.catch {}around the call that establishes permission.|| user[0]when the lookup missed.- Clamping an index into range.
Each one takes a state where the honest answer is stop and launders it into a value the next line will happily use. The bounds check is real. The clamp is the bug. They're on the same line, and that's exactly why it survived three months of me reading past it.
What the fix actually is
It's a deletion, not the sentinel architecture I proposed. That branch has to do what its two neighbours already do - when the index is out of range, identity is lost, so drop it and make the caller re-anchor:
if (_st().activeTabIndex && _st().activeTabIndex > tabCount) {
_st().activeTabIndex = null; // fail closed, like every other exit
return null;
}
Callers already handle this. There's a _assertNotFallingBackToUserTab() that throws a clear "Tab tracking lost - re-run safari_new_tab to recover." The recovery path was built. The clamp was just routing around it.
The cost is honest: sessions that used to get lucky will now throw. In a tool whose entire promise is "we don't touch your tabs," an error message is the correct output for "I don't know which tab is mine." A guess is not.
It's PR #59, open against issue #54 - where I've also left my original root-cause analysis standing, wrong, with a correction under it. The drift between what I remembered and what shipped is the bug; editing the evidence out seemed like the wrong move.
The part I keep thinking about
I filed a detailed, confident, well-structured bug report about code I wrote, and got the root cause wrong - because I reasoned from my mental model instead of from the file. The mental model was a year of accumulated intent. The file was what actually shipped. Those had quietly drifted apart, and the gap between them is precisely where the bug lived.
If I'd handed that issue to an AI agent - or a new contributor - they'd have implemented the sentinel I asked for. Diligently. It already existed. The clamp would still be there, and the user's tabs would still be getting navigated, and the issue would be closed.
The most expensive thing in that whole chain was my confidence.
Have you found one of these in your own code - a "fix" that was actually inventing an answer? I'd genuinely like to collect the pattern. Drop it in the comments.
safari-mcp is MIT-licensed and drives your real, logged-in Safari on macOS. It has 97 tools, and - once #59 lands - one fewer place where it guesses which tab is yours.
Comments
No comments yet. Start the discussion.