Build
Skill Studio-Moser/skills-n-stuff/plugins/claude-instances/skills/build
Product research, market intelligence, and content tools for AI-native teams
npx -y skills add Studio-Moser/skills-n-stuff --skill buildAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Build or refresh a renamed duplicate of /Applications/Claude.app so the user can run multiple Claude Desktop apps side by side, each signed into a different account. Same skill creates a new instance (e.g. "Claude II", "Claude Client X") or rebuilds an existing one to pick up Claude's latest auto-update. Trigger whenever the user wants to add another logged-in Claude account, refresh a duplicate Claude app, or mentions things like "Claude II is out of date", "I need a second Claude for my work account", "make another Claude instance", "duplicate Claude for client X". Invoke with /claude-instances:build.
SKILL.md
19.8 KB, as published. Nobody here has run it
Build Claude Instance
This skill builds a renamed, ad-hoc-signed duplicate of /Applications/Claude.app that:
- Has its own bundle identifier (so macOS treats it as a separate app and lets it run alongside the original)
- Uses its own
~/Library/Application Support/<Name>directory (so it can be signed into a different account) - Optionally has a custom icon (so the user can tell instances apart in Finder/Dock)
- Inherits whatever version Claude.app is currently at — re-run the skill any time the original auto-updates to bring the duplicate forward
The same procedure handles both the first build of an instance and every subsequent refresh; the only difference is whether the target .app already exists.
Inputs to gather
-
Instance name — the display name of the duplicate, e.g.
Claude II,Claude Client X,Claude Personal 2. This becomes the.appfilename (/Applications/<Name>.app), the user-data folder name (~/Library/Application Support/<Name>), and a slug feeds the bundle id. Ask the user if they didn't already say one in their message. -
Custom icon (optional, first time only) — path to a
.icnsfile. If the user doesn't provide one, the instance shares Claude's default icon (telling them apart visually becomes hard, but the app still works). If the user previously provided an icon for this instance name, reuse it without asking — it lives at~/Library/Application Support/claude-instances/icons/<Name>.icns.
Do not invent a name. If you don't have one, ask — keep the question brief and don't survey the user with options.
Preconditions
/Applications/Claude.appexists.- If the instance already exists, it's quit (Cmd-Q, not force-killed). Replacing a running bundle corrupts in-flight state and may log the user out.
NAME="<the instance name>" # e.g. "Claude II"
APP="/Applications/$NAME.app"
ls -d "/Applications/Claude.app" 2>&1
pgrep -fl "$NAME" || echo "$NAME not running"
If it's running, stop and ask the user to Cmd-Q it. Don't pkill yourself — that risks corrupting auth.
Why this is delicate (read before touching)
Claude is an Electron app. Getting a renamed duplicate to launch and keep its own separate login state requires threading five needles, each independently easy to get wrong.
1. Do NOT change CFBundleName
Electron resolves its helper .apps by name derived from CFBundleName, looking for Contents/Frameworks/<CFBundleName> Helper.app, <CFBundleName> Helper (GPU).app, etc. Setting CFBundleName to anything other than Claude makes Electron search for non-existent helpers; the app crashes during startup with FATAL:electron/shell/app/electron_main_delegate_mac.mm:65 Unable to find helper app.
Change only CFBundleIdentifier and CFBundleDisplayName. Leave CFBundleName as Claude. The duplicate will show as "Claude" in the Dock/app switcher but as the chosen name in Finder/Spotlight/window titles. Don't "fix" the Dock label by reverting this — it breaks the app.
2. Ad-hoc sign inside-out, and DROP requirements metadata
A naive codesign --force --deep --sign - strips helper entitlements and breaks V8. The correct order is helpers → frameworks → main bundle.
Use --preserve-metadata=entitlements,flags,runtime where appropriate. Do not include requirements — the original Designated Requirement pins Anthropic's Team ID, and an ad-hoc signature can't satisfy a Team-ID-pinned requirement, so verification fails with "nested code is modified or invalid".
3. Main-app entitlements: add three cs.*, and STRIP every restricted entitlement
Ad-hoc signatures have no Team ID, so library validation fails and V8 crashes with SIGTRAP during v8::Isolate::Dispose. The main binary needs all three:
com.apple.security.cs.allow-jit(already present; keep)com.apple.security.cs.disable-library-validation(add — lets main binary load ad-hoc-signed frameworks/helpers)com.apple.security.cs.allow-unsigned-executable-memory(add — V8 needs this once library validation is off)
Also add disable-library-validation to three of the four helpers: Claude Helper.app, Claude Helper (GPU).app, Claude Helper (Renderer).app. The Plugin helper already ships with it.
You must ALSO drop every restricted entitlement from the ad-hoc entitlements, or the kernel SIGKILLs the process the instant it execs — zero output, no crash report, and codesign --verify still passes (only the runtime AMFI check enforces this). In the unified log (log stream --predicate 'senderImagePath CONTAINS[c] "AMFI"') you'll see AMFI error -424: "The file is adhoc signed but contains restricted entitlements." Restricted entitlements are the ones that require a real provisioning-profile / Team-ID signature; an ad-hoc signature carrying them is fatal.
Current Claude.app (unlike older versions — this is why the skill can't just trust what Anthropic ships) carries several on the main binary: com.apple.application-identifier, com.apple.developer.team-identifier, keychain-access-groups, com.apple.security.virtualization — plus com.apple.security.virtualization on Claude Helper.app. Strip these from every entitlements plist before signing. The duplicate loses shared-keychain items (passkeys/hardware-key, Microsoft SSO); normal web login still persists in the instance's own data dir. Don't assume the restricted set is frozen — the strip_restricted helper in the procedure drops the known offenders (application-identifier, *team-identifier, keychain-access-groups, com.apple.developer.*, com.apple.security.virtualization); the com.apple.security.device.* / personal-information.* App-Sandbox keys are NOT restricted and can stay.
4. Delete CFBundleIconName so a custom icon takes effect on macOS 26+
On macOS 26 (Tahoe) and later, the system prefers CFBundleIconName (an asset name inside Contents/Resources/Assets.car) over the loose CFBundleIconFile → electron.icns path. Claude.app ships with both set. If we leave CFBundleIconName in place, Finder/Dock read the icon out of Assets.car and ignore our swapped electron.icns entirely.
Deleting CFBundleIconName forces Launch Services to fall back to CFBundleIconFile, which is electron.icns and now holds the custom icon. On older macOS this key is absent anyway, so deleting it is safe across versions.
5. Wrap the main executable to pass --user-data-dir on every launch (CRITICAL)
Because CFBundleName stays "Claude", Electron's app.getPath('userData') returns ~/Library/Application Support/Claude — the same path as the original. Without intervention, all instances share the original's login state.
The fix: replace Contents/MacOS/Claude with a shell wrapper that execs the real binary with --user-data-dir. The real binary is renamed to Claude-bin and lives alongside the wrapper:
Contents/MacOS/Claude # shell wrapper (new)
Contents/MacOS/Claude-bin # real Mach-O binary (renamed from Claude)
The wrapper:
#!/bin/bash
DIR="$(cd "$(dirname "$0")" && pwd)"
exec "$DIR/Claude-bin" --user-data-dir="$HOME/Library/Application Support/<Name>" "$@"
Claude-bin must be signed directly (with entitlements + hardened runtime) because codesign can't apply entitlements to a shell script. The bundle-level sign then seals both files as resources.
The build procedure
Substitute the user's chosen instance name for <Name> and a slug (lowercase, spaces → hyphens) for <slug>. Example: Claude II → name Claude II, slug claude-ii, bundle id com.anthropic.claudefordesktop.claude-ii.
Run in order. Stop on any failure.
set -e
NAME="<Name>" # e.g. "Claude II"
SLUG="$(echo "$NAME" | tr '[:upper:] ' '[:lower:]-')" # e.g. "claude-ii"
BUNDLE_ID="com.anthropic.claudefordesktop.$SLUG"
APP="/Applications/$NAME.app"
SRC="/Applications/Claude.app"
FW="$APP/Contents/Frameworks"
DATA_DIR="$HOME/Library/Application Support/$NAME"
ICON_CACHE="$HOME/Library/Application Support/claude-instances/icons"
ICON_FILE="$ICON_CACHE/$NAME.icns"
# 1. Replace any old duplicate with a fresh copy of Claude.app
rm -rf "$APP"
cp -R "$SRC" "$APP"
# 2. Change ONLY identifier and display name. CFBundleName stays "Claude".
# Also delete CFBundleIconName (see needle #4 above).
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $BUNDLE_ID" "$APP/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName $NAME" "$APP/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Delete :CFBundleIconName" "$APP/Contents/Info.plist" 2>/dev/null || true
# strip_restricted: drop entitlements an ad-hoc signature can't legally carry.
# Leaving any of these in → kernel SIGKILLs the app at exec (AMFI -424), silently.
strip_restricted() {
for k in ":com.apple.application-identifier" ":com.apple.developer.team-identifier" \
":keychain-access-groups" ":com.apple.security.virtualization"; do
/usr/libexec/PlistBuddy -c "Delete $k" "$1" 2>/dev/null || true
done
}
# 3. Build augmented main-app entitlements
MAIN_ENT="/tmp/$SLUG-main-ents.plist"
codesign --display --entitlements :- "$SRC" 2>/dev/null | tail -1 > "$MAIN_ENT"
/usr/libexec/PlistBuddy -c "Add :com.apple.security.cs.disable-library-validation bool true" "$MAIN_ENT" 2>/dev/null || true
/usr/libexec/PlistBuddy -c "Add :com.apple.security.cs.allow-unsigned-executable-memory bool true" "$MAIN_ENT" 2>/dev/null || true
strip_restricted "$MAIN_ENT"
# 4. Sign helpers first, inside-out. Add disable-library-validation to the three lacking it.
for h in "Claude Helper.app" "Claude Helper (GPU).app" "Claude Helper (Renderer).app"; do
ENT="/tmp/$SLUG-$(echo "$h" | tr ' ()' '___')-ents.plist"
codesign --display --entitlements :- "$FW/$h" 2>/dev/null | tail -1 > "$ENT"
/usr/libexec/PlistBuddy -c "Add :com.apple.security.cs.disable-library-validation bool true" "$ENT" 2>/dev/null || true
strip_restricted "$ENT"
codesign --force --sign - --entitlements "$ENT" --options runtime "$FW/$h"
done
# Plugin helper already has disable-library-validation; just preserve its entitlements
codesign --force --sign - --preserve-metadata=entitlements,flags,runtime "$FW/Claude Helper (Plugin).app"
# 5. Frameworks — no entitlements, just valid signatures
for f in "Electron Framework.framework" "Mantle.framework" "ReactiveObjC.framework" "Squirrel.framework"; do
codesign --force --sign - "$FW/$f"
done
# 6. Apply the custom icon, if we have one for this instance.
# If the user provided a fresh icon path this run, copy it into the cache first.
# (The "user-provided-icon path" step happens here, before this block:
# mkdir -p "$ICON_CACHE"
# cp "<user-provided path>" "$ICON_FILE"
# )
if [ -f "$ICON_FILE" ]; then
cp "$ICON_FILE" "$APP/Contents/Resources/electron.icns"
fi
# 7. Install the wrapper: move real binary aside, write shell script in its place
mv "$APP/Contents/MacOS/Claude" "$APP/Contents/MacOS/Claude-bin"
cat > "$APP/Contents/MacOS/Claude" <<WRAPPER
#!/bin/bash
DIR="\$(cd "\$(dirname "\$0")" && pwd)"
exec "\$DIR/Claude-bin" --user-data-dir="$DATA_DIR" "\$@"
WRAPPER
chmod +x "$APP/Contents/MacOS/Claude"
# 8. Sign the real binary directly with entitlements (entitlements can't apply to a shell script)
codesign --force --sign - --entitlements "$MAIN_ENT" --options runtime "$APP/Contents/MacOS/Claude-bin"
# 9. Sign the whole bundle (seals the wrapper, icon, and modified Info.plist as resources)
codesign --force --sign - --entitlements "$MAIN_ENT" --options runtime "$APP"
# 10. Re-register with Launch Services + clear icon caches
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -f "$APP"
rm -rf "$HOME/Library/Caches/com.apple.iconservices.store" 2>/dev/null
touch "$APP"
killall iconservicesagent Dock Finder 2>/dev/null
Note the heredoc uses <<WRAPPER (unquoted) so $DATA_DIR interpolates from the parent shell; the \$DIR, \$0, and \$@ references inside are escaped so they get baked into the script literally rather than expanding at heredoc-write time.
Seed configuration from the base app (config only, NOT login)
The .app rebuild never touches $DATA_DIR, so an instance keeps its own login across refreshes. But a brand-new instance starts with no extensions, no MCP servers, and default settings. This step copies the base app's configuration — installed extensions/plugins, MCP servers, UI preferences — into the instance, without copying anything that carries a login. It's safe to run every build: it overwrites only the allow-listed config items and leaves the instance's own auth (cookies, tokens, storage) untouched, so the instance stays signed into whatever account it was on.
The split matters. Copy config; never copy credentials:
| Copy (config) | Never copy (auth / identity) |
|---|---|
Claude Extensions/ (installed plugins/skills) | Cookies*, Trust Tokens*, Tokens* |
Claude Extensions Settings/ | buddy-tokens.json, bridge-state.json |
extensions-installations.json, extensions-blocklist.json | ant-device-registry.json, ant-did, code |
claude_desktop_config.json (MCP servers) | IndexedDB/, *Storage/, Session Storage/, DIPS* |
Preferences, window-state.json, cowork-enabled-cli-ops.json | config.json (opaque; may hold account identity — leave it) |
The allow-list is the source of truth: only these items are ever copied, so an auth file the skill doesn't know about can never leak into the instance. That's the whole reason for an allow-list rather than "copy everything except…".
BASE_DATA="$HOME/Library/Application Support/Claude"
mkdir -p "$DATA_DIR"
CONFIG_ITEMS=(
"Claude Extensions"
"Claude Extensions Settings"
"extensions-installations.json"
"extensions-blocklist.json"
"claude_desktop_config.json"
"Preferences"
"window-state.json"
"cowork-enabled-cli-ops.json"
)
for item in "${CONFIG_ITEMS[@]}"; do
if [ -e "$BASE_DATA/$item" ]; then
rm -rf "$DATA_DIR/$item"
cp -R "$BASE_DATA/$item" "$DATA_DIR/$item"
fi
done
echo "Seeded config into $DATA_DIR (login left as-is)"
If the user explicitly wants the instance to start as a full clone of the base (same account already signed in, to then switch accounts inside the app), that's a different ask — copy the whole $BASE_DATA to $DATA_DIR instead. Default to config-only; only clone wholesale if they say so.
Verify before declaring success
Signature verification passing does NOT prove the app launches. The SIGTRAP-in-V8 crash is only caught by actually running it. Always do all four checks.
# Signature
codesign --verify --strict --verbose "$APP"
# Expect: "valid on disk" + "satisfies its Designated Requirement"
# All three entitlements present on Claude-bin
codesign --display --entitlements :- "$APP/Contents/MacOS/Claude-bin" 2>&1 \
| grep -oE "allow-jit|disable-library-validation|allow-unsigned-executable-memory" | sort -u
# Expect three lines
# CFBundleIconName gone (so custom icon will take effect)
/usr/libexec/PlistBuddy -c "Print :CFBundleIconName" "$APP/Contents/Info.plist" 2>&1
# Expect: 'Print: Entry, ":CFBundleIconName", Does Not Exist'
# Version (should match Claude.app)
/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$APP/Contents/Info.plist"
# Live launch — confirm it stays alive AND uses the per-instance data dir
"$APP/Contents/MacOS/Claude" > /tmp/$SLUG-smoke.log 2>&1 &
BGPID=$!
sleep 5
if kill -0 $BGPID 2>/dev/null; then
pgrep -fl "Claude-bin" | grep -q "user-data-dir=$DATA_DIR" \
&& echo "SUCCESS: $NAME launched with its own data dir" \
|| echo "WARNING: launched but --user-data-dir flag not visible"
pkill -f "$NAME" 2>/dev/null
else
echo "FAILED — exited early"; cat /tmp/$SLUG-smoke.log
fi
Report the new version to the user.
Saving the user's icon (when they provide one)
If the user supplies an icon path this run, save it to the cache before step 6 so future refreshes can find it:
mkdir -p "$ICON_CACHE"
cp "<path the user gave you>" "$ICON_FILE"
Use the original path as-is (allow ~ and shell-style paths — the user might paste a Downloads path or drag-drop into the terminal). If the source file doesn't exist or isn't a .icns, surface the error rather than silently skipping; don't try to convert PNG/JPG inline.
Failure signatures and what they mean
- App appears with the default Claude icon despite providing a custom one —
CFBundleIconNamewasn't deleted, so macOS 26+ is reading the icon out ofAssets.carinstead of ourelectron.icns. Re-check step 2 and confirmPlistBuddy Print :CFBundleIconName ...errors with "Does Not Exist". Then clear icon caches (rm -rf ~/Library/Caches/com.apple.iconservices.store; if that's not enough, the system-level cache at/Library/Caches/com.apple.iconservices.storevia sudo) andkillall iconservicesagent Dock Finder. - Two apps share the same logged-in account — the wrapper isn't in place or isn't being called. Confirm
Contents/MacOS/Claudeis a shell script andClaude-binexists beside it. Checkpgrep -fl "Claude-bin"shows the--user-data-dirflag for this instance's path. Unable to find helper app, immediate exit —CFBundleNamegot changed. Set it back to "Claude".Killed: 9(exit 137) at exec, ZERO output, no crash report,spctlstill shows the app "rejected" — an ad-hoc-signed binary carrying a restricted entitlement. Staticcodesign --verifyPASSES; only the runtime AMFI check kills it. Confirm withlog stream --predicate 'senderImagePath CONTAINS[c] "AMFI"'while launching → look for AMFI-424"adhoc signed but contains restricted entitlements". Fix: runstrip_restrictedon every entitlements plist before signing (step 3/4). Notetimeout N <binary>returning 137 = SIGKILL; 124 = ran the full N seconds = healthy.- SIGTRAP in
v8::Isolate::Dispose/ares_dns_rr_get_ttl— missing entitlements onClaude-bin. Confirm all three (allow-jit,disable-library-validation,allow-unsigned-executable-memory) are on the Mach-O binary, not just the bundle. different Team IDsdyld error — main app or helpers lackdisable-library-validation. Re-sign inside-out.nested code is modified or invalidfromcodesign --verify—requirementswas preserved; drop it from--preserve-metadata.- App shows as "Claude" not the chosen name in Dock — not a failure.
CFBundleNamemust stay "Claude" for Electron to find its helpers.CFBundleDisplayNamehandles Finder/Spotlight.
Edge cases
- First-run Gatekeeper dialog: macOS may prompt "<Name>.app was downloaded from the internet" after re-signing. Normal; user clicks Open.
- Permission error on
/Applications: surface it; don't silently escalate tosudo. - Electron major version upgrade: helper names (
Claude Helper.app, etc.) could theoretically change. Ifls "$FW"shows different sub-.apps, stop and report the actual contents to the user. - Instance data lost after refresh: check that
~/Library/Application Support/<Name>/still exists. The build only rebuilds the.appbundle, never touches user data. - Multiple instances: there's no list-of-instances state; each is identified purely by its
.appname. To enumerate, scan/Applicationsfor.appbundles whoseContents/MacOS/Claude-binexists.