Repository navigation
macOS ignores the desktop app's Local Network permission because it ships Electron's stock executable UUID #18221
Description
What happened
A Claude Code session in T3 Code (Nightly) got "No route to host" (EHOSTUNREACH) for every LAN address: a VMware Fusion VM over SSH and the home router over HTTP. Internet hosts worked, and ARP for the VM was complete. T3 Code (Nightly) was enabled under Privacy & Security > Local Network; toggling it and restarting changed nothing. Uninstalling Signal Desktop fixed it at once. It started around when Signal 8.30.0 (2026-10-07) and a T3 Nightly moved to the same Electron version.
Diagnosis
macOS Local Network privacy identifies an app partly by its main executable's Mach-O LC_UUID. T3 Code ships Electron's prebuilt executables unchanged, so it shares those UUIDs with every app on the same Electron release. Here macOS resolved T3 by PID, then matched Signal by UUID and applied Signal's missing permission (see Evidence).
- The Nightly main executable, Signal 8.30.0 and the official Electron 44.4.5 arm64 build all have UUID
4C4C44F8-5555-3144-A1B8-270B7145EDA0. The helpers match stock as well. Alpha 0.0.45 has the stock 44.4.2 UUID. - Apple TN3179: "If your main executable has no UUID, or shares a UUID with other programs, local network privacy may behave weirdly. To fix that, make sure your main executable has a UUID and that it's unique." (TN3178 covers the same issue.)
- electron-builder 26.15.6 copies the binaries as is and has not addressed this (MacOS local network privacy Mac-O UUID collision electron-userland/electron-builder#9158, closed as stale).
scripts/build-desktop-artifact.tshas noafterPackhook. fix(desktop): declare macOS local network usage #11922 is needed but does not prevent this. - It cuts both ways: users can lose LAN access, or get it without granting it to T3.
Suggested fix: an electron-builder afterPack hook in createBuildConfig that rewrites the LC_UUID of the main and helper executables, per Mach-O slice, with a value derived from bundle id + executable name + CPU type. It must run before scripts/sign-macos.ts, so signing seals the patched binaries. Add a CI check that no packaged UUID equals stock Electron's. Netcatty#1041, PwrAgent#2342 and openplc-editor#1179 shipped this fix. Cost: the small launcher no longer symbolicates against Electron's dSYM, and users see one new Local Network prompt.
Hook sketch (tested on a copy of stock Electron 44.4.5: UUIDs changed, re-signed ad hoc, codesign --verify --deep --strict passed, binary runs; not tested with Developer ID signing)
const fs = require("node:fs");
const path = require("node:path");
const crypto = require("node:crypto");
const LC_UUID = 0x1b;
const MH_MAGIC_64 = 0xfeedfacf;
const FAT_MAGIC = 0xcafebabe;
const FAT_MAGIC_64 = 0xcafebabf;
function derivedUuid(seed) {
const b = crypto.createHash("sha256").update(seed).digest().subarray(0, 16);
b[6] = (b[6] & 0x0f) | 0x80; // RFC 9562 version 8 (custom)
b[8] = (b[8] & 0x3f) | 0x80; // RFC 9562 variant
return b;
}
function patchSlice(buf, off, seed) {
if (buf.readUInt32LE(off) !== MH_MAGIC_64) throw new Error("not a 64-bit Mach-O slice");
const cputype = buf.readUInt32LE(off + 4);
const ncmds = buf.readUInt32LE(off + 16);
let p = off + 32;
for (let i = 0; i < ncmds; i++) {
if (buf.readUInt32LE(p) === LC_UUID) {
derivedUuid(`${seed}|${cputype}`).copy(buf, p + 8);
return;
}
p += buf.readUInt32LE(p + 4);
}
throw new Error("no LC_UUID load command");
}
function patchFile(file, seed) {
const buf = fs.readFileSync(file);
const magic = buf.readUInt32BE(0);
if (magic === FAT_MAGIC || magic === FAT_MAGIC_64) {
const entry = magic === FAT_MAGIC ? 20 : 32;
for (let i = 0; i < buf.readUInt32BE(4); i++) {
const e = 8 + i * entry;
const off = magic === FAT_MAGIC ? buf.readUInt32BE(e + 8) : Number(buf.readBigUInt64BE(e + 8));
patchSlice(buf, off, seed);
}
} else {
patchSlice(buf, 0, seed);
}
fs.writeFileSync(file, buf);
}
exports.default = async function afterPack(context) {
if (context.electronPlatformName !== "darwin") return;
const { productFilename, id } = context.packager.appInfo;
const app = path.join(context.appOutDir, `${productFilename}.app`);
const targets = [path.join(app, "Contents/MacOS", productFilename)];
const frameworks = path.join(app, "Contents/Frameworks");
for (const name of fs.readdirSync(frameworks)) {
if (name.endsWith(".app") && name.includes(" Helper")) {
targets.push(path.join(frameworks, name, "Contents/MacOS", name.slice(0, -4)));
}
}
for (const file of targets) patchFile(file, `${id}|${path.basename(file)}`);
};Steps to reproduce
Observed:
- Install T3 Code (Nightly) 0.0.46-nightly.20261011.2967 and Signal Desktop 8.30.0 (both Electron 44.4.5). Signal has no Local Network permission; T3 does.
- Have an agent in T3 run
ssh <LAN host>orcurl http://<router IP>. - Result:
No route to host. After uninstalling Signal, step 2 works.
Without T3 or Signal (not run; needs a signing identity):
- Package a small Electron app that connects to a LAN host and shows the result, twice with
@electron/packager --electron-version=44.4.5, as Probe A and Probe B with different bundle ids. Sign both, install them, and check thatdwarfdump --uuidshows the same UUID for both. - Run
open -a "Probe A"and deny the prompt. Don't launch from Terminal, because Terminal's children always get LAN access. - Run
open -a "Probe B". Expected: its own prompt. Actual:EHOSTUNREACH, and the log showsfound bundle id <Probe A's id> by UUID. - Give Probe B a unique UUID (hook above) and re-sign: it now behaves independently.
Version
0.0.46-nightly.20261011.2967 (tag v0.0.46-nightly.20261011.2967, 8d199b4); also affects 0.0.45 (Alpha, Electron 44.4.2)
Environment
macOS 27.0 (26A428, Darwin 27.0.0) arm64; desktop app with its local server; Claude Code 2.1.296
Evidence
# unified log, subsystem com.apple.networkextension (UserEventAgent), on every attempt
LocalNetwork: found bundle id com.t3tools.t3code by PID
LocalNetwork: found bundle id org.whispersystems.signal-desktop by UUID 4C4C44F8-5555-3144-A1B8-270B7145EDA0
# agent's ssh attempts: 24 failures, 2026-10-10T15:30Z to 2026-10-11T12:27Z (ARP entry for the VM complete)
ssh: connect to host <vm-host> port 22: No route to host
$ dwarfdump --uuid <main executable>
UUID: 4C4C44F8-5555-3144-A1B8-270B7145EDA0 (arm64) T3 Code (Nightly).app
UUID: 4C4C44F8-5555-3144-A1B8-270B7145EDA0 (arm64) Signal.app
UUID: 4C4C44F8-5555-3144-A1B8-270B7145EDA0 (arm64) Electron.app from official electron-v44.4.5-darwin-arm64.zip
UUID: 4C4C4469-5555-3144-A1DE-D0AE69E1CC4A (arm64) T3 Code (Alpha).app, same as official Electron 44.4.2Related issues
#11922 (usage description, needed but not sufficient); #8466 and #18222 are about bundle ids, not LC_UUID; upstream: electron-userland/electron-builder#9158. No existing T3 issue covers this.
Fix applied or workaround
Uninstalled Signal Desktop; nothing else changed. Workaround for others: uninstall other apps that ship Electron's stock binary on the same Electron version as T3 (apps on custom Electron builds, such as VS Code, are unaffected).
Filed by
claude (claude-opus-5-5) via Claude Code inside T3 Code, following the t3 triage playbook (session not started with t3 triage)
Activity
juliusmarminge commentedon Oct 11, 2026
Note
Grok responding on behalf of Julius.
Upstream Electron / macOS Local Network privacy — not a T3 bug. Closing.
createBuildConfigdeclaresNSLocalNetworkUsageDescription(#11922) but has noafterPackhook, and nothing rewrites Mach-OLC_UUID. Nightly ships Electron 44.4.5 stock binaries, so every app on that release shares the UUID; macOS matches by it (TN3179). Tracked in electron-builder#9158 (closed stale).Workaround: uninstall or update the colliding app (here Signal 8.30.0), or grant it Local Network permission. Custom-Electron apps (e.g. VS Code) do not collide.
Not #11922 (usage description only) or #9724 (Chromium LNA / Okta FastPass). If
EHOSTUNREACHpersists with no other stock-Electron app installed, comment and we can reopen.ArchitektApx commentedon Oct 11, 2026
Thanks for looking at this. I don't think anything upstream will fix this for T3 soon.
On the workarounds: Signal 8.30.0 is its latest release and T3 main is also on 44.4.5, so updating isn't possible right now. Granting Signal Local Network access only fixes T3 by giving Signal LAN access too. The same collision will come back whenever T3 shares an Electron release with another app that ships Electron's stock binary, and it also works the other way, giving T3 LAN access the user never granted.
I know Apple has no tool for setting a UUID after linking. The hook only rewrites the 16 UUID bytes before signing, so the normal signature and notarization cover the result. openplc-editor#1179 verified the prompt and LAN connection end to end on macOS 26 with this approach, and Netcatty and PwrAgent ship it too.
I've opened electron-userland/electron-builder#10297 so it can be fixed there for everyone. Would you consider reopening this for the hook in the meantime?