데이터 손실 버그에 대한 수정 사항이 4일 동안 녹색 PR에 있었습니다. 해당 창의 모든 설치에는 여전히 버그가 있었습니다.

작성자

카테고리:

← 피드로
DEV Community · אחיה כהן · 2026-07-21 개발(SW)

On July 17 I opened the most serious issue my project has ever had, wrote the fix the same morning, and pushed it as a pull request. CI went green on Node 20, 22 and 24 within minutes. Zero review comments. mergeStateStatus: CLEAN.

Then it sat there.

I merged it today — July 21. Four days.

For those four days the fix existed, was correct, was tested, and was visible to nobody. Every npx safari-mcp in that window installed the version with the bug. The npm counter says roughly 1,400 people a week pull this thing. The fix was done and the users still had the bug, and those two facts sat side by side without touching each other.

I want to be precise about why, because the interesting part isn’t “I was busy.”

The bug, briefly

safari-mcp lets an AI agent drive the Safari you’re already logged into. That’s the value and that’s the danger: you’re using the browser at the same time as the agent. The one promise the project makes is the agent never touches a tab it didn’t open.

That promise had a hole. When the tracked tab index ran past the end of the window — you closed a tab, or tore one into its own window — the resolver clamped the index to the last tab:

if (_st().activeTabIndex && _st().activeTabIndex > tabCount) {
  console.error(`[Safari MCP] Tab ghost proactive fix: clamping to ${tabCount}`);
  _st().activeTabIndex = tabCount;
}

Enter fullscreen mode Exit fullscreen mode

The last tab in the window is not our tab. It’s whatever you happen to have open there. So at the exact moment the code discovers it no longer knows where its tab is, it points the session at one of yours — and logs the words “proactive fix” while doing it.

The fix is four lines: fail closed. Drop ownership, return null, let the next call open a fresh tab.

Small diff. Green CI. Four days.

What actually held it

Not review capacity. I’m the only reviewer; there was no queue.

Not risk. The change makes an unsafe path safe; the worst case of shipping it is that the agent opens a redundant tab.

What held it was that merging was the fourth step of a five-step ritual and only the first step was automated. Merge, bump version, write the changelog entry, cut a GitHub release, and only then does the Publish workflow fire and npm gets the fix. Steps 2–4 are me, at a keyboard, in a mood to do release chores.

CI told me the code was good. Nothing told me the code was stuck. Those are different signals and I only had one of them.

The tell I ignored

Here’s the part that made me write this instead of quietly shipping.

While cutting today’s release I opened CHANGELOG.md and found the previous release, v2.15.3, had no entry at all. Shipped July 15. The release commit touched exactly two files: package.json and package-lock.json.

Three real fixes went out that day — clicks landing on the wrong tab, ARIA combobox typeahead never loading, a focus-helper queue that poisoned itself after a timeout, the last one contributed by an outside developer. Anyone reading the changelog to decide whether to upgrade saw nothing between 2.15.2 and 2.15.4. The contributor’s fix was invisible.

I didn’t skip that entry on purpose. I skipped it because it’s step 3 of the same manual ritual, and step 3 has no test.

So the pattern isn’t “a PR was slow.” The pattern is: the parts of shipping that a machine watches are reliable, and the parts that only a human watches decay — silently, and in the same direction every time. My CI has 57 tests. My release process has zero.

The uncomfortable comparison

There’s a well-worn instinct in solo open source that goes: don’t automate the release, you’ll ship something bad by accident. Manual = careful.

I believed that. Look at what manual actually bought me:

Automated Manual Code correctness 57 tests, 3 Node versions — Packaging packaging.test.mjs fails CI if an imported file isn’t published — Registry sync server.json version synced + published on release — Merge decision — 4-day gap Changelog entry — silently missing Cutting the release — mood-dependent

Every column with a machine in it held. Every column with only me in it drifted. The “careful” half is the half that failed, and it failed quietly, which is worse than failing loudly.

The honest version of the instinct isn’t manual is careful. It’s manual feels careful because you were paying attention at the moment you did it, and you have no record of the moments you weren’t.

What I’m changing

Not full auto-release-on-merge. I still want a human deciding when a release exists, because “these three fixes belong together” is a judgment call and I don’t have a robot with taste.

What I want is for the gaps to be loud:

  1. A changelog check in CI. If the diff touches safari.js or index.js and doesn’t touch CHANGELOG.md, CI complains. That’s the test step 3 never had. It’s ten lines and it would have caught the 2.15.3 hole on the day it happened.
  2. A stale-green-PR alarm. Any PR that is CLEAN + passing + zero-review-comments for more than 48 hours gets surfaced, loudly, wherever I’ll actually see it. Not to force a merge — to make “still holding this” a decision I re-make rather than a default I drift into.
  3. Release notes generated from the changelog, so the GitHub release and the changelog can’t disagree — one source, two renders.

None of that is clever. That’s the point. The failure wasn’t clever either.

The thing I’d actually generalize

If you maintain something alone, look at your last five releases and ask a narrow question: which steps between “merged” and “a user can install it” have no automated observer?

Not “which steps are manual” — plenty of manual steps are fine. Which manual steps would fail without producing a symptom you’d notice.

A missing changelog entry produces no error. A green PR sitting for four days produces no error. A release you didn’t cut produces no error. They produce the absence of something, and absence doesn’t page anybody.

My whole test suite is built to catch code that does the wrong thing. Not one of those 57 tests can catch code that does the right thing where nobody can reach it.

v2.15.4 is on npm now — the tab-index path fails closed, and the 2.15.3 changelog entry has been backfilled. Source: github.com/achiya-automation/safari-mcp

What’s the gap in your pipeline that has no observer? I’m genuinely collecting these — the ones I find myself are always the boring ones, and the interesting ones seem to come from other people’s setups.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다