Repository navigation
macOS: ShadowCode runs as a native Mac app - #7
Merged
Merged
Conversation
- Voice capture: cpal is a macOS dependency too (CoreAudio). - Control socket probe: macOS has no SOCK_NONBLOCK/SOCK_CLOEXEC; set both with fcntl(2) before the descriptor is used. - Lints that only fire on macOS: a test-only worktree helper is gated like its Linux-only caller, a test's struct update is allowed (its other fields exist on Linux only), and the Wayland window variant is allowed unused off Linux (macOS sizes like X11). - CI: a macOS job builds the interface and runs clippy for the whole workspace with warnings as errors. - docs/MACOS_PORT_ESTIMATE.md records phase 1 and the first phase 2 findings (604 of 638 engine unit tests pass on macOS). Verified on macOS 15.8.1 (iMac19,2, Xcode 26.3): cargo clippy --workspace --all-targets --locked -- -D warnings is clean. Linux: fmt, clippy and the affected modules' tests pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Workspace file and folder opens pass O_NOFOLLOW to cap-std. On Linux cap-std opens with openat2 and that is enough; on macOS it resolves the path itself and followed a symlink at the last component before the flag reached the final openat, so mutation_leaf_opens_refuse_symlinks_and_nonregular_files failed there. Every such open now also sets cap-std's own FollowSymlinks::No (cap-fs-ext 3.4.6, the same release as cap-std). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The workspace root is canonical, so an absolute path naming it through a symlink was refused as outside the workspace. On macOS that is every temporary folder (/var is /private/var) and any project reached through a symlinked folder; 14 engine tests failed there for this reason. When the plain prefix check fails, the deepest existing ancestor is canonicalized and the check runs again; a path that really resolves outside, by .. or by a symlink inside the workspace, stays refused. Tested on both systems. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ixed - Absolute paths naming the workspace through a symlinked folder stay inside (on macOS /var is /private/var, as is every temporary folder). - Remote access refuses a not-yet-existing folder under a symlinked profile path too (it resolved only existing paths; both systems). - Backups locate their open folder with fcntl(F_GETPATH) on macOS, where there is no /proc/self/fd, so a symlink swapped in at the folder's name is never written through (secrets included). - Rules exported to a vendor CLI are recognised as linked by the links' real location, not their spelling. - Test fixtures name their temporary folders canonically, as product paths are; the import-label test no longer requires the redacted address. - docs/MACOS_PORT_ESTIMATE.md: phase 2 progress and what remains. macOS 15.8.1 (iMac19,2): engine unit tests 640 passed, 0 failed; clippy -D warnings clean. Linux: 664 passed; fmt and clippy clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On macOS the temporary and data folders are reached through /var, a link to /private/var. Sessions are matched against Workspace::open, and managed checkouts are checked against their canonical path, so both now store the real path. The macOS integration failures drop from 118 to 43. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- SQLite inspection: macOS has no /proc/self/fd. Names under a reserved prefix stand for the held directory, and SQLite's own open/stat calls for them become openat/fstatat on that directory (never following a final link); every other name passes through unchanged. - Terminals: openpty on macOS, and session members found through proc_listallpids and getsid, so hang-up reaches job-control groups. - Local models: installed memory from hw.memsize (was 0 GB on macOS). - Rulebook: a profile item's id stays relative when discovery names it by its real path. - Tests: process liveness through one helper (proc_pidinfo on macOS) instead of /proc, which passed vacuously there; real paths where the service reports them; no GNU-only touch -d or setsid(1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- worktrees::cleanup (keeps a checkout deleted outside ShadowCode recoverable) now runs on macOS: the held directory is named with F_GETPATH and the exclusive rename is renameatx_np(RENAME_EXCL). The looser non-Linux fallback is removed. - Tests: forged worktree records use the real checkouts path; two paused-clock stream tests are Linux-only (the paused clock races real loopback I/O on macOS; the timing logic is the same everywhere). - docs/MACOS_PORT_ESTIMATE.md: integration status and how to run it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Settings and history live under ~/Library/Application Support/ShadowCode (cache under ~/Library/Caches) on macOS; an XDG variable set on purpose still wins. - Opened from Finder or the Dock, the app inherits launchd's minimal PATH. Without a terminal it now asks the login shell for its PATH once at startup (5 s limit, common install folders otherwise), so Homebrew Git, Node and the vendor CLIs are found. - Stopping a command or vendor agent signals its descendants on macOS too (proc_listchildpids), not only its process group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The agent's command tool used bubblewrap or Landlock, so on macOS it ran commands unsandboxed. It now runs them under sandbox-exec with a profile that mirrors the bubblewrap one: - Reading only in system and developer folders, the prefixes of the programs on PATH outside the home folder (Homebrew), the project, the toolchain folders and the command's scratch. The rest of the home folder, other users and other volumes are hidden; even stat is refused except on the folders leading to an allowed one. - HOME is an empty folder in the scratch linking to the read-only toolchains; TMPDIR is the scratch. Credential folders are never linked and stay unreadable inside the project too. - Writes only in the project and the scratch (and xcrun's cache files). - Network off allows no IP networking, not even localhost; on allows it; the allow-list allows only the proxy's loopback port. - DEVELOPER_DIR is set so Apple's git/clang shims don't run xcodebuild (3.6 s and warnings per call inside the sandbox; 0.08 s with it). - Signals only within the sandbox; system services only for user lookups, logging, file events and (with network) DNS and certificates. 'Require sandbox' and the allow-list refuse commands when sandbox-exec cannot run. Settings, status and doctor describe the macOS sandbox. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The keyring was the freedesktop Secret Service over D-Bus only. On macOS the same "Move to keyring" now keeps a key in the login Keychain as a generic password (service ShadowCode, account <profile>/<name>) through security-framework (MIT OR Apache-2.0, macOS only). Settings › Accounts names the Keychain there. A live round trip against the real Keychain is an ignored test (it needs a logged-in desktop session; SSH sessions are refused by macOS). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The preview found the project's servers through /proc, so it was left out of macOS builds. On macOS it lists this user's listening TCP sockets with the system's lsof and reads each process's working folder and executable through libproc; the proxy and the rest are unchanged. Platform-neutral servers/own_ports_now/listening_now pick the source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The preview integration test, the vendor login supervision tests and two vendor unit tests were Linux-only because they read /proc to see whether a process is alive or reaped. They use kill(pid, 0) or, where process identity matters, proc_pidinfo (start time, zombie state) and proc_pidpath on macOS, and pass there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Model servers, language servers, embeddings, vendor agents and login helpers used Linux's PR_SET_PDEATHSIG so a crash never leaves them running. macOS has no equivalent: the child forks a watcher (no inherited descriptors) that waits on kqueue and kills the child if ShadowCode exits first. One helper, process::die_with_parent, now serves every call site, with a test that SIGKILLs a stand-in parent. - scripts/build-llama.cpp.sh builds a Metal runtime on macOS (embedded shaders, the same loadable CPU variants, @loader_path), works with macOS's bash 3.2, and installs to ~/Library/Application Support/ ShadowCode/llama.cpp, where the app now looks; the bundled runtime is Contents/Resources/llama.cpp. - tauri.macos.conf.json: app and dmg bundles with the runtime, ad-hoc signed, macOS 12 or later. Info.plist explains microphone use. - Finder's -psn_ argument is ignored instead of failing the parse. - The silent-download test is Linux-only like the stream tests: its paused clock races real loopback I/O on macOS (CI failure). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The macOS runtime compiles its Metal shaders at build time (default.metallib next to llama-server). Compiling them at run time took 55 s on first use and hung outside a desktop session (over SSH or as a background service); --list-devices now answers in 0.2 s either way. Only the libraries llama-server and llama-cli use are shipped. - llama.cpp names Metal devices MTL0; the backend is reported as metal and Settings › Local models says Metal. - scripts/build-macos-app.sh builds the runtime when needed and then ShadowCode.app and the .dmg with it in Contents/Resources/llama.cpp. The runtime mapping lives in packaging/macos/bundle-runtime.conf.json, used only there: tauri-build refuses a missing resource path, which broke the macOS CI build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The local engine's hardware line said "via MTL" for llama.cpp's Metal device ids. docs/MACOS_PORT_ESTIMATE.md records what now works on macOS (the app bundle, sandbox, Keychain, processes, preview, Metal runtime) and what remains (Developer ID signing and notarization, release automation and updates, Apple silicon testing, a real model run). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ShadowCode for macOS is distributed on GitHub only, not notarized: - release-macos.yml builds ShadowCode.app/.dmg on Apple silicon (macos-15) and Intel (macos-15-intel) runners when a release is published, smoke-tests the app (version, doctor's sandbox check, the bundled Metal runtime, hdiutil verify) and attaches both images with ShadowCode_<version>_macos.sha256, never replacing an existing file. Pull requests that touch macOS packaging only build. - scripts/install-macos.sh picks the Mac's image (Rosetta-aware), refuses it unless the release checksum matches, installs into /Applications (~/Applications when not writable) and links ~/.local/bin/shadowcode. curl downloads are not quarantined, so Gatekeeper does not block it. - The Linux publisher, on a retry of an already published release, leaves those macOS files alone (a draft still refuses them). - The updater knows the macOS app and shows the installer command. - README: macOS install, the browser-download "Open Anyway" step, and where data lives on a Mac. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Redaction's high-entropy rule masked whole absolute paths such as /Volumes/NVME1TB/shadowcode-macos/task-demo (long, mixed case, digits and '/', like base64), so a command's folder showed as "[redacted secret]" on its approval card; on macOS that is most project paths. A path under a well-known root (/Users, /Volumes, /private, /home, ..., also after file://) is now judged one segment at a time: a secret that is itself a segment is still hidden, and base64 that only contains '/' is judged whole as before. - Whether a local model fits was judged from installed memory only. The engine now also reads memory free right now (MemAvailable on Linux; free, inactive, speculative and purgeable pages on macOS), and a model that runs in RAM, or in Apple silicon's shared memory, says "Only N GB of memory is free right now; close other apps first or it will run very slowly" when it would not fit. Seen on an 8 GB iMac whose iOS Simulator held most of its memory: Granite 3B ran at 6 s per token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ShadowfetchStudio
marked this pull request as ready for review
October 17, 2026 16:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes ShadowCode build, install and run as a native macOS app. Tested on an Intel iMac (macOS 15, Xcode 26.3):
scripts/build-macos-app.shproducesShadowCode.appandShadowCode_<version>_<arch>.dmg(ad-hoc signed, macOS 12+); the app opens from Finder, doctor passes, and the local engine runs llama.cpp on Metal.What changed for macOS
.github/workflows/macos.ymlruns clippy and the unit tests onmacos-15.~/Library/Application Support/ShadowCode(cache in~/Library/Caches); sessions and worktrees record real paths (/varis/private/var).PATHis adopted once at startup.HOMEwith read-only toolchains, writes only in the project and scratch, network off/on/allow-list proxy).security-framework).proc_listchildpids; helpers die with ShadowCode through a kqueue watcher (Linux keepsPR_SET_PDEATHSIG)./proc.scripts/build-llama.cpp.shbuilds a Metal runtime with precompiled shaders; bundled inContents/Resources/llama.cpp./procnow use portable helpers.Distribution (GitHub only)
.github/workflows/release-macos.yml: on a published release, builds the.dmgonmacos-15(Apple silicon) andmacos-15-intel, smoke-tests it and attaches both plusShadowCode_<version>_macos.sha256. On PRs touching macOS packaging it only builds.scripts/install-macos.sh:curl … | bashinstaller that checks the checksum and installs to/Applications; curl downloads are not quarantined, so the un-notarized app opens without a Gatekeeper block. Browser downloads need Privacy & Security › Open Anyway once (README).Not in this PR
release/trust/policy); they are checked by SHA-256 over HTTPS.See
docs/MACOS_PORT_ESTIMATE.md› Progress for details.🤖 Generated with Claude Code