Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions desktop/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ scripts/
**/python_reference/
Desktop/Sources/Resources/ffmpeg
Desktop/Sources/Resources/node
Desktop/Sources/Resources/libnode.*.dylib

# Local Rust helper build output
codex-proxy/target/
Expand Down
11 changes: 10 additions & 1 deletion desktop/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -587,8 +587,17 @@ if [ -n "$SIGN_IDENTITY" ]; then
fi
# Sign the bundled node binary with developer identity + Node.entitlements
# (macOS requires executables inside app bundles to be properly signed)
NODE_BIN="$APP_BUNDLE/Contents/Resources/Omi Computer_Omi Computer.bundle/node"
NODE_BUNDLE_DIR="$APP_BUNDLE/Contents/Resources/Omi Computer_Omi Computer.bundle"
NODE_BIN="$NODE_BUNDLE_DIR/node"
if [ -f "$NODE_BIN" ]; then
# Sign any libnode dylib staged alongside the binary (Homebrew dynamic builds).
# Dylibs don't carry entitlements at runtime (macOS reads them from the main executable only);
# sign with --options runtime alone, no --entitlements.
for libnode_dylib in "$NODE_BUNDLE_DIR"/libnode.*.dylib; do
[ -f "$libnode_dylib" ] || continue
substep "Signing $(basename "$libnode_dylib")"
codesign --force --options runtime --sign "$SIGN_IDENTITY" "$libnode_dylib"
done
substep "Signing bundled node binary"
codesign --force --options runtime --entitlements Desktop/Node.entitlements --sign "$SIGN_IDENTITY" "$NODE_BIN"
fi
Expand Down
29 changes: 29 additions & 0 deletions desktop/scripts/prepare-agent-runtime.sh
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,35 @@ stage_local_node() {
cp -f "$node_bin" "$NODE_RESOURCE"
chmod +x "$NODE_RESOURCE"
xattr -cr "$NODE_RESOURCE" 2>/dev/null || true

# Homebrew node is a small stub that dlopen()s libnode.X.dylib via @loader_path.
# Copy the dylib alongside the binary so it resolves at both validation and runtime.
local libnode_name
libnode_name=$(otool -L "$node_bin" 2>/dev/null \
| awk 'match($0, /libnode\.[0-9]+\.dylib/) {print substr($0, RSTART, RLENGTH); exit}')
if [ -n "$libnode_name" ]; then
local libnode_src=""
local node_bin_dir
node_bin_dir="$(dirname "$(realpath "$node_bin")")"
for candidate in \
"$node_bin_dir/../lib/$libnode_name" \
"$node_bin_dir/$libnode_name" \
"$(brew --prefix 2>/dev/null)/lib/$libnode_name"; do
if [ -f "$candidate" ]; then
libnode_src="$(realpath "$candidate")"
break
fi
done
if [ -z "$libnode_src" ]; then
echo "ERROR: node requires $libnode_name but it was not found near $node_bin or in Homebrew lib." >&2
exit 1
fi
cp -f "$libnode_src" "$(dirname "$NODE_RESOURCE")/$libnode_name"
chmod u+w "$(dirname "$NODE_RESOURCE")/$libnode_name"
xattr -cr "$(dirname "$NODE_RESOURCE")/$libnode_name" 2>/dev/null || true
Comment on lines +151 to +153

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 $(dirname "$NODE_RESOURCE") is evaluated three times in a row. Capturing it in a local variable makes the intent clearer and avoids repeated subshell overhead.

Suggested change
cp -f "$libnode_src" "$(dirname "$NODE_RESOURCE")/$libnode_name"
chmod u+w "$(dirname "$NODE_RESOURCE")/$libnode_name"
xattr -cr "$(dirname "$NODE_RESOURCE")/$libnode_name" 2>/dev/null || true
local node_resource_dir
node_resource_dir="$(dirname "$NODE_RESOURCE")"
cp -f "$libnode_src" "$node_resource_dir/$libnode_name"
chmod u+w "$node_resource_dir/$libnode_name"
xattr -cr "$node_resource_dir/$libnode_name" 2>/dev/null || true

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

log "Staged $libnode_name alongside node (Homebrew dynamic build)"
fi
Comment on lines +138 to +155

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 @rpath vs @loader_path — staged path may not resolve at runtime

The PR description says the Homebrew stub references @loader_path/libnode.X.dylib, but a related upstream issue (nexu-io/open-design#1275) shows modern Homebrew node uses @rpath/libnode.X.dylib instead. With @rpath, dyld iterates the binary's LC_RPATH entries — which for a Homebrew node stub are typically @loader_path/../lib and the absolute /opt/homebrew/lib path.

Placing the dylib in the same directory ($(dirname "$NODE_RESOURCE")/) satisfies @loader_path/ in the RPATH only if that exact entry exists. If the RPATH has only @loader_path/../lib plus the absolute Homebrew path, the staged copy would be silently ignored and the binary would still resolve the dylib from /opt/homebrew/lib. For a fully self-contained bundle, the dylib needs to be placed to match an actual LC_RPATH entry, or the install name of the staged dylib needs to be patched with install_name_tool -add_rpath. Can you confirm that otool -l /opt/homebrew/bin/node | grep -A2 LC_RPATH shows @loader_path/ (without ../lib) as one of the entries?


log "Staged local Node $("$NODE_RESOURCE" --version) from $node_bin"
}

Expand Down
Loading