Repository navigation
feat(runtime): bundle mitmdump with packaged Agent Code - #121
Conversation
PR 1 of the bundled-runtime-tools effort (#119, #120). Pure scaffolding, no behaviour change yet — establishes the conventions PR 2/PR 3 build on: - `third_party/<tool>/{manifest.json,README.md,LICENSE.md,.gitignore}` pinning each tool's version, per-arch sha256, URL/source layout. Binaries are never committed; the manifest plus fetch/build scripts are the single source of truth. - `scripts/runtime-tools/` with working fetch + verify for mitmproxy (real upstream archive, real hashes for mitmproxy 12.2.3 darwin arm64/x86_64) and stubs for tmux that will be implemented in PR 3. - npm scripts `runtime:fetch:mitmproxy`, `runtime:verify`, `runtime:prepare:mac`. Deliberately NOT wired into `postinstall` so dev `npm i` stays cheap (mitmproxy is ~52 MiB per arch). - `src/main/setup/runtimeTools.ts` defines the resolver API surface (`resolveBundledTool`, `getPlatformKey`) returning null for now; PR 2 wires mitmdump extraction, PR 3 wires tmux. - `CLAUDE.md` documents the `vendor/` (source references, never shipped) vs `third_party/` (runtime artifacts, shipped with releases) distinction so the two folders do not blur together over time. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires up the runtime resolver scaffolded in the previous commit so packaged Agent Code no longer requires a user-installed mitmproxy for Claude proxy streaming. Closes #119. What changes: - `scripts/copy-packaged-resources.mjs` copies each `third_party/<tool>/cache/<platform>/*` plus the manifest into `out/main/runtime/<tool>/<platform>/` so electron-builder's asarUnpack glob can land them at a real filesystem path. Missing caches log a warning (dev) rather than fail; release builds run `runtime:prepare:mac` first and that step is strict. - `electron-builder.yml` adds `out/main/runtime/**/*` to `asarUnpack`. - `src/main/setup/runtimeTools.ts` implements the resolver: reads the packaged manifest, locates the platform archive, extracts on first call into `app.getPath('userData')/runtime/mitmproxy-<ver>/`, writes the `.installed` marker last so a crash mid-extract is recoverable, and best-effort prunes older versions to stop unbounded growth across upgrades. Per-version in-process mutex prevents concurrent ClaudeSession spawns from racing tar extraction. - `src/providers/claude/runtime/claudeSession.ts` resolves the bundled path before each proxy startup and threads it through `mitmDumpPath` on `createProxyServer`. Falls back to the setup-cached path and finally to the headless package's own PATH/Homebrew chain when bundled is unavailable. The headless package stays Electron-agnostic. - Setup gate: `brew` is no longer required (packaged users without Homebrew can now reach the app). `mitmdump` shows as "Bundled" with the new `SetupToolSource = 'bundled' | 'system'` field; the install button is suppressed for bundled tools and the SetupGate UI labels them clearly. Bundled tools also skip the "missing optional" prompt the gate uses to nag users about Homebrew installs. - `src/shared/types/setup.ts` gains the `source` field on `SetupToolStatus`. Verification (run in worktree): - `npm run runtime:prepare:mac` — fetch all + verify --strict (pass) - `npm run build:app` — clean (warns about empty tmux cache as intended; tmux ships in #120) - Extracted bundled archive into /tmp and ran `mitmproxy.app/Contents/MacOS/mitmdump --version` -> "Mitmproxy: 12.2.3 binary" — confirms the resolver's archive layout assumption. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…spaces, atomic extract Three fixes from the PR #121 review, all must-fix: 1. `copy-packaged-resources.mjs` was using `new URL(import.meta.url) .pathname`, which returns the percent-encoded URL path. A contributor whose checkout lives under a path with a space would compute a `repoRoot` that does not exist on disk; every `fileExists()` would return false; the runtime cache copy would silently no-op; the packaged DMG would ship without the bundled mitmproxy archive — with no build warning. Switched to `fileURLToPath(import.meta.url)`, matching the pattern already used in the sibling fetch/verify scripts and in `claude-code-headless`' addon resolver. 2. `claudeSession.ts` was resolving the bundled mitmdump path before ever consulting `CLAUDE_HEADLESS_MITMDUMP`. Since `createProxyServer` short-circuits on `mitmDumpPath` before reading the env var, the documented "env override wins" contract in `runtimeTools.ts` was being silently broken — a contributor pointing the env var at a debug/instrumented mitmdump would simply not get it. Now we early-return on env override and let `claude-code-headless` consume it through its own existing chain. 3. `runtimeTools.ts` was extracting on top of any pre-existing `mitmproxy-<ver>/` directory. mitmproxy.app's PyInstaller layout includes symlinks (Python framework Versions/X/Current and similar); BSD `/usr/bin/tar` overwriting a half-extracted leftover from a previous interrupted install can leave mixed file-vs- symlink state that the marker check cannot detect. Now we extract into a sibling tmp directory, verify the inner executable is present, rm-rf the canonical dir, and rename the tmp dir into place — atomic on macOS because both paths live on the same filesystem (userData). Also added an explicit KNOWN LIMITATION comment on `cleanupOldMitmproxyVersions` documenting the same-userData multi- install race (item #4 in the review). Decision: ship v1 without a cross-process advisory lock; revisit if it surfaces in practice. Verified: - `npm run build:app` clean. - The three other concerns flagged in the review (resolver path swap, hardened runtime entitlements, setup-gate brew-flip regressions) were independently confirmed safe by the reviewer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Pushed
Also: explicit Build: Test plan items 1, 2, 4 from the PR body still need to be exercised against the live Electron app before merge. |
Closes #119. Part of the bundled runtime-tools effort; tmux follows in a separate PR (#120).
Summary
Packaged Agent Code now ships its own `mitmdump` so Claude proxy streaming works without a Homebrew-installed mitmproxy. The bundled archive (`mitmproxy 12.2.3`, ~52 MiB compressed) extracts lazily on first proxy session into `userData/runtime/mitmproxy-/` and the resolver returns the absolute path of the inner Mach-O. The reusable `claude-code-headless` package stays Electron-agnostic: Agent Code main resolves the path and passes it through `mitmDumpPath`.
What's in the diff
New npm scripts
```
runtime:fetch:mitmproxy download pinned archive (current platform; --all for both)
runtime:verify:mitmproxy re-verify cache (non-strict by default)
runtime:verify:tmux placeholder-aware until #120
runtime:verify both, default non-strict
runtime:build:tmux stub; full impl in #120
runtime:prepare:mac CI entry: fetch --all + verify --strict (mitmproxy)
```
`postinstall` is NOT wired to fetch — dev `npm i` stays cheap. Release CI calls `runtime:prepare:mac` explicitly.
Verification
```
Mitmproxy: 12.2.3 binary
Python: 3.14.4
OpenSSL: OpenSSL 3.5.5 27 Jan 2026
Platform: macOS-26.2-arm64-arm-64bit-Mach-O
```
Size impact
DMG grows by ~52 MiB (compressed mitmproxy archive). Installed-on-disk after first proxy session: ~87 MB in `userData/runtime/mitmproxy-/`. Old versions get cleaned up on subsequent launches.
Test plan
🤖 Generated with Claude Code