Skip to content

Portability: Linux/Windows platform enablement (Tier B/C — parked) #496

Description

@Juliusolsson05

Why this exists

Companion to the "any-Mac hardening" issue linked below. That issue covers what breaks on a second Mac; this one is the genuine platform-enablement project — everything required for the app to actually run on Linux (and eventually Windows). Today the app is macOS-only by design: bundled tooling, credential storage, window chrome, and the keyboard layer are all mac-shaped.

This is intentionally parked — it's not a bug pass, it's a new-platform effort with a real cost. Filing it now so the failure points are captured while fresh; it should get its own explicit green-light and scoping before work starts.

Full audit writeup lives at temp/portability-audit-issue.md in-repo.


Findings

B1 · CRITICAL (Linux) — No Linux/Windows bundled binaries anywhere; getPlatformKey() returns null off-darwin

src/main/setup/runtimeTools.ts:84-102 · third_party/{mitmproxy,tmux,cloudflared}/manifest.json (only darwin-arm64 + darwin-x86_64)

On Linux, getPlatformKey() returns nullresolveBundledTool() yields null → mitmproxy (Claude proxy-streaming spine, #119), tmux (pane persistence, #120), and cloudflared (remote tunnel) are all absent. tmux/cloudflared have a no-PATH-fallback policy, so those features are simply dead; mitmproxy falls back to PATH but nothing's bundled. scripts/runtime-tools/fetch-*.mjs also build a linux-x64 key that has no manifest entry, so even fetching fails.

Fix: add linux-x86_64/linux-arm64 (and Windows) entries to each manifest with per-arch sha256 + URL templates, extend getPlatformKey, and teach the fetch/verify + copy-packaged-resources.mjs those platforms.

B2 · CRITICAL (Linux) — node-pty has no Linux prebuild; postinstall compiles it from source

package.json postinstall · node-pty prebuilds ship only darwin-* + win32-*

A fresh npm install on Linux compiles pty.node from source, requiring python3 + make + a C++ toolchain. On a box without build-essential, install fails at postinstall with no prebuilt fallback.

Fix: pin/verify a Linux prebuild per arch, or document the required toolchain.

B3 · CRITICAL (Linux) — No linux/win electron-builder target exists at all

electron-builder.yml (only a mac: block) · every package.json script is --mac; release.yml is macOS-only

There's no config path to produce a Linux/Windows artifact, and entitlements.mac.plist + hardenedRuntime are mac-specific.

Fix: add linux (AppImage/deb) and win (nsis) target blocks + matching runtime:prepare:* scripts, gated behind B1/B2.

B4 · HIGH — Primary global keydown handler is hard-bound to metaKey (Cmd) → entire keyboard surface dead on Linux/Windows

src/renderer/src/workspace/tile-tree/useKeybinds.ts:159 (const cmd = e.metaKey)

This handler drives the command palette, tile navigation, tab open/close, and dispatch-digit chords. On Linux/Windows the primary modifier is Ctrl (metaKey = Super/Win), so every shortcut becomes unreachable. The codebase already uses the right pattern elsewhere (CommandPalette.tsx:1088, DebugBundleNotePrompt.tsx:72 use metaKey || ctrlKey).

Fix: const cmd = e.metaKey || e.ctrlKey (or a platform-aware primaryModifier helper).

B5 · HIGH — Claude/Codex usage is hard-wired to macOS Keychain via security; hard throw off-darwin

src/main/usage/claudeUsage.ts:34-50 (throw 'Claude usage currently requires macOS Keychain credentials.' + security find-generic-password) · same shape in codexUsage.ts

The usage/limits panel can never populate on Linux/Windows — a permanent thrown error, with no fallback to Claude Code's on-disk credential file (~/.claude/.credentials.json) or libsecret/secret-tool.

Fix: add per-platform credential readers (file-based first, then libsecret/Windows Credential Manager); degrade to a clean "not available" state, not an exception. (The remote-auth secret in secret.ts already uses a portable 0o600 file pattern to mirror.)

B6–B10 · MEDIUM/LOW (Linux)

  • B6titleBarStyle: 'hiddenInset' (mainWindow.ts:155) is mac-only chrome; off-mac you get double chrome or a frameless window with no min/close/max controls the renderer never draws. Fix: platform-branch the title-bar style + inset padding.
  • B7 — PATH augmentation prepends only Homebrew dirs /opt/homebrew/bin, /usr/local/bin (toolchain.ts:93-94); on Linux these usually don't exist. Fix: append /usr/bin, ~/.local/bin etc. on Linux.
  • B8 — mitmdump absolute-path fallback list omits /usr/bin/mitmdump (proxyServer.ts:636-637), the standard Linux location. Fix: command -v before the hardcoded list.
  • B9caffeinate keep-awake and the dictation Fn default binding have no Linux equivalent (guarded, degrade cleanly). Fix: wire systemd-inhibit / a platform-aware default binding.
  • B10STATE_DIR ignores $XDG_CONFIG_HOME (paths.ts:18) — per-user-correct but not XDG-honoring on Linux. Fix: honor XDG_CONFIG_HOME when set. (Overlaps A10 in the companion issue.)
  • B11 (note) — The provider/terminal core (HeadlessTerminal, node-pty) is genuinely display-free, but the app shell is an Electron BrowserWindow, so a truly headless Linux/SSH host (no X/Wayland) can't launch the GUI at all. Inherent to the GUI; stated for expectation-setting.

Suggested execution order (only if green-lit)

  1. B1 → B2 → B3 — make it run (binaries, prebuild, build target).
  2. B4 + B5 + B6 — make it usable (keyboard, credentials, window chrome).
  3. B7–B10 — the polish tail.

Already-verified cross-platform (no work needed)

From the sweep: mitmproxy CA trust is env-var based (NODE_EXTRA_CA_CERTS/SSL_CERT_FILE/…), not Keychain / system-trust mutation — fully portable. Ports are dynamic (listen(0)). Atomic same-dir renames avoid EXDEV. Fonts have generic fallback chains. shell.openExternal/showItemInFolder use cross-platform Electron APIs (no raw xdg-open shelling). editorFs already path-separates on win32.


🤖 Drafted from a 6-agent portability sweep; see temp/portability-audit-issue.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions