#21075: fix(media): use sips on Node.js + darwin to prevent Photos TCC prompt
size: S
Cluster:
Node and macOS Enhancements
## Problem
On macOS 15, when OpenClaw receives a media file (e.g. image from Telegram), the Node.js gateway process triggers a macOS TCC Photos permission dialog:
> "node" wants to access your Photos library
**Root cause:** `prefersSips()` in `image-ops.ts` only selects `sips` when running under Bun on darwin. When running under Node.js (the common case for the npm-installed gateway), it falls back to `sharp`. The `sharp` package loads Apple's native HEIC/ImageIO codec libraries, which macOS 15 associates with the Photos framework, triggering the TCC permission request.
## Fix
Remove the `isBun()` guard (and the now-unused `isBun()` helper). On any darwin platform, `sips` is already available at `/usr/bin/sips` and handles JPEG/PNG/HEIC correctly without touching Photos framework APIs.
Export `prefersSips()` so it can be unit-tested directly.
```ts
// Before
process.env.OPENCLAW_IMAGE_BACKEND !== "sharp" && isBun() && process.platform === "darwin"
// After
process.env.OPENCLAW_IMAGE_BACKEND !== "sharp" && process.platform === "darwin"
```
## Behavior
| Runtime | Before | After |
|---------|--------|-------|
| Bun + darwin | sips ✅ | sips ✅ |
| Node.js + darwin | sharp ❌ (TCC prompt) | sips ✅ |
| Linux/Windows | sharp ✅ | sharp ✅ |
`OPENCLAW_IMAGE_BACKEND=sharp` still overrides to sharp on all platforms for users who explicitly want it.
## Tests (15 cases)
All branches of `prefersSips()` are now directly unit-tested via `vi.stubEnv` + `vi.spyOn(process, 'platform', 'get')`:
| # | Condition | Expected |
|---|-----------|----------|
| 1 | `OPENCLAW_IMAGE_BACKEND=sips` + darwin | `true` |
| 1 | `OPENCLAW_IMAGE_BACKEND=sips` + linux | `true` |
| 1 | `OPENCLAW_IMAGE_BACKEND=sips` + win32 | `true` |
| 2 | `OPENCLAW_IMAGE_BACKEND=sharp` + darwin | `false` (override wins) |
| 2 | `OPENCLAW_IMAGE_BACKEND=sharp` + linux | `false` |
| 5 | `OPENCLAW_IMAGE_BACKEND=sharp` + darwin + Bun-like runtime | `false` (regression guard) |
| 3 | no env + darwin (Node.js) | `true` ← **core fix** |
| 3 | no env + darwin (Bun) | `true` (no regression) |
| 3 | empty string env + darwin | `true` |
| 4 | no env + linux | `false` |
| 4 | no env + win32 | `false` |
| 4 | empty string env + linux | `false` |
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Removes the Bun-only guard from `prefersSips()` so that Node.js on darwin also uses the native `sips` tool instead of `sharp` for image operations. This prevents macOS 15 from showing a TCC Photos permission prompt when the gateway processes media files, since `sharp` loads Apple's HEIC/ImageIO codec libraries that trigger the Photos framework association.
- Removes `isBun()` helper function from `src/media/image-ops.ts:22-25`
- Updates `prefersSips()` logic to check only platform (darwin) and env override, not runtime
- Adds test documentation explaining the behavior change
- `OPENCLAW_IMAGE_BACKEND=sharp` env var still allows explicit sharp usage on all platforms
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with no risk
- The change is minimal and well-contained: removes a runtime check that was incorrectly limiting `sips` usage to Bun only. The fix correctly addresses the TCC permission prompt issue by allowing Node.js on darwin to use the native `sips` tool. The logic is straightforward, backward compatible (env override still works), and the test documents the expected behavior.
- No files require special attention
<sub>Last reviewed commit: ff6daf8</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
## Incidental fix
Also fixes a pre-existing TypeScript error (`TS2339`) in `src/cli/update-cli.test.ts:284` introduced by #21071: wraps `runDaemonInstall.mockResolvedValue` with `vi.mocked()` to match the pattern used elsewhere in the same file.
Most Similar PRs
#6064: fix(daemon): prefer bundled node from install-cli.sh over system node
by joyshmitz · 2026-02-01
75.4%
#18112: fix(daemon): gateway install on macOS ignores fnm/nvm node (#18090)
by yinghaosang · 2026-02-16
74.8%
#17237: fix(update): guard post-install imports after npm global update
by tdjackey · 2026-02-15
74.6%
#17456: fix(test): stabilize media root guard test against tmpdir HOME overlap
by widingmarcus-cyber · 2026-02-15
73.9%
#23308: fix(browser): accept upload paths that traverse symlinked tmp dirs
by SidQin-cyber · 2026-02-22
73.8%
#17912: fix: configure git to use HTTPS instead of SSH for GitHub URLs
by MisterGuy420 · 2026-02-16
73.8%
#19885: test(gateway,browser): isolate tests from ambient OPENCLAW_GATEWAY_...
by NewdlDewdl · 2026-02-18
73.8%
#20330: Fix SSH tunnel startup on Windows by resolving ssh from PATH
by graysurf · 2026-02-18
73.5%
#19063: CI/macOS: disable Vitest vmForks for TS tests to stop mock-state le...
by agisilaos · 2026-02-17
73.5%
#18811: fix(media): require file extension for ambiguous MEDIA: path detection
by aldoeliacim · 2026-02-17
73.2%