Codex Desktop mini window popout renders Application Error on macOS 26.4

Resolved 💬 4 comments Opened Apr 17, 2026 by StuartAJ Closed Apr 17, 2026

Summary

On Codex Desktop for macOS, the thread mini-window/popout no longer renders the conversation. It opens a popout window, but the popout immediately displays the app error boundary instead of the thread content.

Local investigation also found a separate terminal-font issue in the same app install: the terminal was configured to use Geist Mono, but that font was not installed locally, so it silently fell back to another monospace font. Installing font-geist-mono resolves that local font issue.

Environment

  • App: Codex Desktop from /Applications/Codex.app
  • Codex app version: 26.415.21839
  • CFBundleVersion: 1763
  • Bundle id: com.openai.codex
  • macOS: 26.4 / build 25E246
  • Hardware: Apple M4, arm64
  • Electron: 41.2.0
  • Chrome: 146.0.7680.179
  • Node: 24.14.0
  • Sentry release in local scope: codex@26.415.21839
  • Tags in local Sentry scope: buildFlavor=prod, bundle=webview, host=app, preRelease=false
  • Active app-server process: /Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled

There is also an older ~/Applications/Codex.app at 26.409.20454, but the running process is the newer /Applications/Codex.app build above.

Reproduction steps

  1. Open a local Codex Desktop thread.
  2. Use the thread action to open the mini window / popout.
  3. Observe the popout window.

Expected behavior

The popout should render the selected thread in the mini-window view.

Actual behavior

The popout opens, but it shows the app's error boundary / Application Error message instead of thread content.

The local Sentry scope captured this renderer error:

Error: Thread context is unavailable for non-thread routes
React ErrorBoundary Error

The stack in the Sentry scope points into the production webview bundle, including:

app://-/assets/index-kiOLixS3.js
app://-/assets/use-model-settings-DxAqUfLj.js
app://-/assets/vscode-api-BM-wzBx9.js
app://-/assets/app-server-manager-signals-AIlnLWLG.js

Investigation notes

I extracted the running app bundle from:

/Applications/Codex.app/Contents/Resources/app.asar

The relevant compiled webview bundle is:

webview/assets/index-kiOLixS3.js

From the extracted production bundle:

  • The mini-window action dispatches open-thread-overlay with hostId, conversationId, and title.
  • It then calls maybe-resume-conversation and broadcast-conversation-snapshot for the selected conversation.
  • The overlay route is /thread-overlay/:conversationId.
  • The overlay route component renders the thread content with allowMissingConversation: true and showExternalFooter: true.
  • A route matching helper does recognize /thread-overlay/:conversationId as having a conversation id.
  • However, the runtime error indicates that a component or hook below the overlay route is still calling thread-context-only code while the current route is considered a non-thread route.

That suggests the regression is likely one of these:

  1. /thread-overlay/:conversationId is missing the same thread context provider used by normal thread routes such as /local/:conversationId or /remote/:conversationId.
  2. A child component now rendered in the overlay path, possibly a footer/model/settings/tooling component, assumes thread context and calls the guarded thread-context hook.
  3. The route classification/helper that gates thread context does not include the overlay route even though the overlay route has a conversationId.

Suggested fix direction

One of the following should fix the popout path:

  • Wrap /thread-overlay/:conversationId in the same thread context provider used by the normal thread routes.
  • Or make overlay-specific children avoid the guarded thread-context hook and use explicit conversationId / hostId props instead.
  • Add a regression test that mounts or navigates to /thread-overlay/:conversationId and verifies it does not render the error boundary.

Duplicate search

I searched open and closed issues/PRs for the exact error string and related mini-window/popout terms and did not find an obvious duplicate for this specific macOS Desktop error:

"Thread context is unavailable for non-thread routes"
"Open in mini window"
"thread-overlay"
"popout"

A related but different issue exists for Windows popout resizing (#16180), but this report is about the macOS popout crashing with the thread-context error.

Related local terminal-font finding

This appears separate from the popout crash, but happened in the same app install.

The app state had the light theme Code font set to:

"Geist Mono", ui-monospace, "SFMono-Regular"

Before installing the font, local font enumeration did not show Geist Mono, so the integrated terminal could not render with the configured font and silently fell back. Installing the font fixed local font resolution:

brew install --cask font-geist-mono
fc-list | rg -i 'Geist Mono|GeistMono'

After installation, Geist Mono is visible under ~/Library/Fonts/ and the terminal should use it after an app/window refresh.

This aligns with the behavior discussed in #10620: the terminal uses the configured Code font. A small product improvement would be to surface a warning or diagnostic when the configured UI/Code font is unavailable on the system.

View original on GitHub ↗

4 Comments

StuartAJ · 4 months ago

Additional local investigation / candidate fix:

I attempted a local hotfix against the packaged renderer bundle in 26.415.21839. The crash appears to happen during overlay bootstrap because the renderer route scope is computed from window.location.pathname before the initialRoute=/thread-overlay/<conversationId> navigation has settled.

In the packaged bundle, the route-scope classifier in webview/assets/diff-view-mode-CDxb8Wc9.js maps these paths to route kinds:

function p({ pathname, routeTemplate }) {
  const conversationId = match(localThread, pathname)?.params.conversationId
    ?? match('/thread-overlay/:conversationId', pathname)?.params.conversationId
    ?? match(hotkeyThread, pathname)?.params.conversationId;

  if (conversationId != null) {
    return { conversationId, pathname, routeKind: 'local-thread', routeTemplate };
  }
  // ... otherwise home/new-thread/remote/other
}

The desktop main process creates the HUD window with initialRoute:

createWindow({
  appearance: 'hud',
  initialRoute: `/thread-overlay/${conversationId}`,
  ...
})

Other overlay-aware code already checks initialRoute directly, but this route-scope classifier does not. If the packaged app initially loads at / or /index.html with ?initialRoute=/thread-overlay/<id>, the route scope can briefly be home/other instead of local-thread, and thread-context atoms then throw:

Error: Thread context is unavailable for non-thread routes

A local candidate patch was to make the route-scope classifier normalize the bootstrap path through initialRoute before matching thread routes:

function p({ pathname, routeTemplate }) {
  let effectivePathname = pathname;

  try {
    if (typeof window !== 'undefined') {
      const initialRoute = new URLSearchParams(window.location.search).get('initialRoute');
      if (initialRoute && (pathname === '/' || pathname === '/index.html' || pathname === 'index.html')) {
        effectivePathname = initialRoute;
      }
    }
  } catch {}

  const conversationId = match(localThread, effectivePathname)?.params.conversationId
    ?? match('/thread-overlay/:conversationId', effectivePathname)?.params.conversationId
    ?? match(hotkeyThread, effectivePathname)?.params.conversationId;

  if (conversationId != null) {
    return {
      conversationId,
      pathname: effectivePathname,
      routeKind: 'local-thread',
      routeTemplate,
    };
  }

  // continue existing remote/home/new-thread/other logic using effectivePathname
}

I verified this patch is syntactically valid when applied to the extracted bundle and that it changes only the route classifier.

StuartAJ · 4 months ago

Follow-up from local validation of the workaround on macOS 26.4 / Codex 26.415.21839:

The popout route patch alone was not sufficient for a locally modified macOS app bundle because Electron and hardened runtime enforcement add two more requirements:

  1. Contents/Info.plist has ElectronAsarIntegrity for Resources/app.asar. After changing and repacking app.asar, Electron aborts at startup unless this hash is updated to the SHA-256 of the ASAR header JSON.
  2. Native modules inside the packed ASAR also need to be signed consistently with the outer app. In this build, node_modules/better-sqlite3/build/Release/better_sqlite3.node and node_modules/node-pty/build/Release/pty.node existed inside the packed ASAR and were still OpenAI-signed. Electron extracts them to temp paths such as /var/folders/.../T/.com.openai.codex.*; dyld then rejects them with mapping process and mapped file (non-platform) have different Team IDs if the outer app has been locally re-signed.

Validated local workaround:

  • Patch the route-scope logic to treat ?initialRoute=/thread-overlay/<id> as the effective pathname during compact/popout bootstrap.
  • Sign native Mach-O files inside the extracted ASAR tree before repacking (better_sqlite3.node, pty.node, and spawn-helper if present).
  • Repack app.asar.
  • Update ElectronAsarIntegrity.Resources/app.asar.hash to the SHA-256 of the repacked ASAR header JSON, not the raw file hash.
  • Sign loose Mach-O files, nested frameworks/apps, then the outer app with one Team ID.

After that, direct launch succeeded, no new crash report was written, and the runtime-extracted temp better_sqlite3 bundle showed the same TeamIdentifier as the outer app.

StuartAJ · 4 months ago

Follow-up from local patch testing: the launch/signing side is now separate from the remaining popout error.

The popout error is reproducible as a renderer route-scope mismatch. The compact/popout window can load through app://-/index.html?initialRoute=..., and the route scope classifier can still classify popout routes as other. Components in the popout then read the thread/workspace context atoms and hit:

Thread context is unavailable for non-thread routes

The local patch that should address this has two parts:

  1. Extend the route classifier to treat the hotkey popout routes as real app routes:
  • /hotkey-window -> home
  • /hotkey-window/thread/:conversationId -> local-thread
  • /hotkey-window/remote/:taskId -> remote-thread
  • preserve the existing initialRoute handling when the actual browser pathname is / or /index.html.
  1. Add a defensive fallback in the thread/workspace context atoms so an other route returns the same home/workspace context instead of throwing during compact-window startup or transition. This prevents the red error boundary while still allowing proper thread context when the route is classified correctly.

Local validation after patching the packaged app:

codesign --verify --deep --strict --verbose=2 /Applications/Codex.app
/Applications/Codex.app: valid on disk
/Applications/Codex.app: satisfies its Designated Requirement

The latest local ASAR integrity hash after this route/context patch is:

b4a2293a5baf044de04b6aae5272fc2e04380d2b8eb1bd247ec0a0dfbf516fe7

This is still a local binary patch rather than an upstream source PR, but it narrows the product fix to route classification for compact/hotkey popout windows plus a non-crashing context fallback.

StuartAJ · 4 months ago

This was fixed with 26.415.30602