Skip to content

fix: NSWindow main-thread crash in native macOS GUI - #126

Merged
samueltuyizere merged 5 commits into
routatic:mainfrom
stefanoverna:fix/gui-nswindow-main-thread-crash
Jul 23, 2026
Merged

fix: NSWindow main-thread crash in native macOS GUI#126
samueltuyizere merged 5 commits into
routatic:mainfrom
stefanoverna:fix/gui-nswindow-main-thread-crash

Conversation

@stefanoverna

Copy link
Copy Markdown
Contributor

What

The GUI dashboard crashed on macOS 14+ with an NSWindow main-thread violation because systray.Run() spawned the webview from a goroutine, which AppKit doesn't allow.

Fix

Removed the systray integration and call the webview directly from the main goroutine. The systray added complexity without providing essential functionality — the webview dashboard already serves the same purpose.

Testing

  • Built and verified GUI launches without crash on macOS 15
  • No regression on Linux/Windows (systray was already no-op there)

Comment thread cmd/routatic-proxy/start_gui_darwin_nocgo.go Outdated
Comment thread cmd/routatic-proxy/start_gui_other.go Outdated
Comment thread cmd/routatic-proxy/main.go Outdated
@kilo-code-bot

kilo-code-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Code Review Roast 🔥

Verdict: No Issues Found | Recommendation: Merge

Oh wait, this PR is actually clean. I need to sit down. I had my flamethrower warmed up and everything.

📊 Overall: Like finding a unicorn in production — I didn't think clean PRs existed anymore, but here we are.

Files Reviewed (7 files)
  • cmd/routatic-proxy/main.go - no issues
  • cmd/routatic-proxy/start_gui_darwin.go - no issues
  • cmd/routatic-proxy/start_gui_darwin_nocgo.go - no issues
  • cmd/routatic-proxy/start_gui_other.go - no issues
  • internal/gui/assets/app.js - no issues
  • internal/gui/server.go - no issues
  • internal/gui/server_test.go - no issues
Previous Review Summaries (4 snapshots, latest commit 4f91d72)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 4f91d72)

Verdict: No Issues Found | Recommendation: Merge

Oh wait, this PR is actually clean. I need to sit down. I had my flamethrower warmed up and everything.

📊 Overall: Like finding a unicorn in production — I didn't think clean PRs existed anymore, but here we are.

Files Reviewed (7 files)
  • cmd/routatic-proxy/main.go - no issues
  • cmd/routatic-proxy/start_gui_darwin.go - no issues
  • cmd/routatic-proxy/start_gui_darwin_nocgo.go - no issues
  • cmd/routatic-proxy/start_gui_other.go - no issues
  • internal/gui/assets/app.js - no issues
  • internal/gui/server.go - no issues
  • internal/gui/server_test.go - no issues

Previous review (commit 88225a1)

Verdict: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
⚠️ warning 1
Issue Details (click to expand)
File Line Roast
cmd/routatic-proxy/main.go 778 CLI command claims success when catalog is missing, returning nil instead of error

🏆 Best part: The systray antipattern was excised with surgical precision. Moving to a direct webview call is the right architecture — no more system tray drama.

💀 Worst part: The CLI returning success on catalog failure. A tool that claims victory when it can't even list models is more optimistic than a VC during a funding round.

📊 Overall: Like a car with a new engine but the check engine light permanently disabled. It runs, but you'll never know when it's coasting on fumes.

Files Reviewed (4 files)
  • cmd/routatic-proxy/main.go - 1 issue
  • cmd/routatic-proxy/start_gui_darwin.go - no issues
  • cmd/routatic-proxy/start_gui_darwin_nocgo.go - no issues
  • cmd/routatic-proxy/start_gui_other.go - no issues

Fix these issues in Kilo Cloud

Previous review (commit ae13de7)

Verdict: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
🚨 critical 2
💡 suggestion 0
🤏 nitpick 1
Issue Details (click to expand)
File Line Roast
cmd/routatic-proxy/start_gui_darwin_nocgo.go 10 cancel() fires immediately after function returns, killing all servers on Linux without CGO before they serve a request
cmd/routatic-proxy/start_gui_other.go 10 Same catastrophic behavior on every non-darwin platform — servers die before the startup log is printed
cmd/routatic-proxy/main.go 390 Comment indented one level shallower than the code it describes

🏆 Best part: Removing the systray dependency entirely — that was cruft. Moving to a direct webview call on macOS is the right architecture.

💀 Worst part: The non-CGO openGUI paths are now antimatter for the proxy. Every server is started, then immediately murdered by an overzealous cancel() call. You've built a distributed system that self-destructs in under a millisecond.

📊 Overall: The Darwin CGO path is slick. The rest of the planet's platforms were apparently an afterthought — the proxy now works exactly as well as my motivation on a Friday afternoon.

Files Reviewed (4 files)
  • cmd/routatic-proxy/main.go – 1 issue (signature change propagates cancel to non-blocking paths)
  • cmd/routatic-proxy/start_gui_darwin.go – no issues (blocking path, cancel behavior is correct)
  • cmd/routatic-proxy/start_gui_darwin_nocgo.go – 1 critical issue
  • cmd/routatic-proxy/start_gui_other.go – 1 critical issue

Fix these issues in Kilo Cloud

Previous review (commit 0006426)

Verdict: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
🚨 critical 2
💡 suggestion 0
🤏 nitpick 1
Issue Details (click to expand)
File Line Roast
cmd/routatic-proxy/start_gui_darwin_nocgo.go 10 cancel() fires immediately after function returns, killing all servers on Linux without CGO before they serve a request
cmd/routatic-proxy/start_gui_other.go 10 Same catastrophic behavior on every non-darwin platform — servers die before the startup log is printed
cmd/routatic-proxy/main.go 390 Comment indented one level shallower than the code it describes

🏆 Best part: Removing the systray dependency entirely — that was cruft. Moving to a direct webview call on macOS is the right architecture.

💀 Worst part: The non-CGO openGUI paths are now antimatter for the proxy. Every server is started, then immediately murdered by an overzealous cancel() call. You've built a distributed system that self-destructs in under a millisecond.

📊 Overall: The Darwin CGO path is slick. The rest of the planet's platforms were apparently an afterthought — the proxy now works exactly as well as my motivation on a Friday afternoon.

Files Reviewed (4 files)
  • cmd/routatic-proxy/main.go – 1 issue (signature change propagates cancel to non-blocking paths)
  • cmd/routatic-proxy/start_gui_darwin.go – no issues (blocking path, cancel behavior is correct)
  • cmd/routatic-proxy/start_gui_darwin_nocgo.go – 1 critical issue
  • cmd/routatic-proxy/start_gui_other.go – 1 critical issue

Fix these issues in Kilo Cloud


Reviewed by step-3.7-flash · Input: 65.6K · Output: 14.2K · Cached: 212.1K

Review guidance: REVIEW.md from base branch main (truncated)

@stefanoverna

Copy link
Copy Markdown
Contributor Author

All issues addressed:

  1. Non-CGO cancel() → the bot was incorrect here (the parameter was _ ignored), but the signature was indeed awkward. Refactored: openGUI now returns (<-chan struct{}, error) on all platforms. CGO path spawns the webview in a goroutine and returns a channel that closes when the window is dismissed. Non-CGO paths return nil channel (only SIGINT stops). No cancel plumbing leaks into platform files.
  2. Comment indentation → fixed as part of the refactor.

Build passes on all platforms.

Comment thread cmd/routatic-proxy/main.go Outdated
…tdown

- Spawn webview in a goroutine so NSWindow runs on the main thread,
  fixing a crash on macOS when opening the native GUI window.

- Return a done channel from openGUI instead of passing a cancel
  function; the channel closes when the native window is dismissed,
  enabling clean shutdown on window close.

- Fix 'Test model' dropdown to show configured model_overrides and
  model_family_overrides from /api/proxy/config.

- Fix 'Refresh catalog' by passing CatalogDir and CatalogSourceURL
  to the GUI server.

- Fix 'Send' to proxy through POST /api/test/send on the GUI server,
  avoiding CORS issues. Includes body size limit (1 MiB), io.Copy
  error handling, and table-driven test coverage.
@stefanoverna
stefanoverna force-pushed the fix/gui-nswindow-main-thread-crash branch from a430924 to c1c9eb5 Compare July 23, 2026 09:27
@stefanoverna

Copy link
Copy Markdown
Contributor Author

@samueltuyizere the Kilo review was rate limited... but we should be good to go now :)

@samueltuyizere

Copy link
Copy Markdown
Collaborator

@samueltuyizere the Kilo review was rate limited... but we should be good to go now :)

taking a quick look into this @stefanoverna

stefanoverna and others added 3 commits July 23, 2026 12:20
The native GUI (webview) crashed on macOS with:
  NSInternalInconsistencyException: NSWindow should only be
  instantiated on the main thread!

Root cause: systray.Run() calls its onReady callback from a goroutine
(not the main thread), but webview.New() creates an NSWindow which
macOS 14+ strictly requires on the main thread.

This bug was present since the Unified start command PR but never
surfaced because the Homebrew release binary is built with
CGO_ENABLED=0 (make dist), which excludes the darwin&&cgo build tag
and uses the nocgo fallback that just prints the URL. The crash only
happens with make build-ui (CGO_ENABLED=1).

Fix: remove systray integration, call webview directly from the main
goroutine. Window close triggers clean shutdown via context cancel.
The webview approach had main-thread issues and added a heavy dependency.
Switch to getlantern/systray which was already in the codebase but unwired.
Dashboard now opens in the default browser; tray icon provides Open/Quit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@samueltuyizere samueltuyizere left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@samueltuyizere
samueltuyizere merged commit bc5d020 into routatic:main Jul 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants