惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Simon Willison's Weblog
Simon Willison's Weblog
Google DeepMind News
Google DeepMind News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
P
Proofpoint News Feed
Recent Announcements
Recent Announcements
MongoDB | Blog
MongoDB | Blog
U
Unit 42
云风的 BLOG
云风的 BLOG
Recorded Future
Recorded Future
G
Google Developers Blog
I
InfoQ
Blog — PlanetScale
Blog — PlanetScale
A
About on SuperTechFans
Jina AI
Jina AI
量子位
宝玉的分享
宝玉的分享
The Cloudflare Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 聂微东
Last Week in AI
Last Week in AI
WordPress大学
WordPress大学
美团技术团队
The Hacker News
The Hacker News
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tailwind CSS Blog
博客园 - 司徒正美
博客园 - 叶小钗
Hugging Face - Blog
Hugging Face - Blog
P
Palo Alto Networks Blog
博客园_首页
阮一峰的网络日志
阮一峰的网络日志
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
The GitHub Blog
The GitHub Blog
Y
Y Combinator Blog
Vercel News
Vercel News
Martin Fowler
Martin Fowler
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Forbes - Security
Forbes - Security
Attack and Defense Labs
Attack and Defense Labs
Google DeepMind News
Google DeepMind News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Azure Blog
Microsoft Azure Blog
P
Privacy International News Feed
G
GRAHAM CLULEY
The Last Watchdog
The Last Watchdog
C
Cyber Attacks, Cyber Crime and Cyber Security
AI
AI
V2EX - 技术
V2EX - 技术

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Claude Desktop com duas contas no macOS: como rodar pessoal e empresa lado a lado
Rafael Pazin · 2026-05-06 · via DEV Community

TL;DR: Claude Desktop não suporta múltiplas contas nativamente. A solução é copiar o .app, modificar o wrapper de lançamento para apontar para um user-data-dir diferente, e re-assinar ad-hoc. O CLI continua único em ~/.claude/. Dois gotchas críticos: nunca altere CFBundleName no Info.plist (o Electron crasha) e remova CFBundleIconName se quiser ícone customizado (o macOS prioriza o Asset Catalog sobre o .icns).


Sabe quando você tem dois chips no celular pra separar zap do trabalho do zap da família? É exatamente isso que precisei resolver no Claude Desktop. A conta pessoal tem conectores que a corporativa não oferece, e ficar deslogando e relogando virou aquela tortura chinesa moderna.

Spoiler: deu certo. Mas levou uma tarde, dois crashes do Electron e uma armadilha de cache de ícone que me fez questionar minhas escolhas de vida. Bora ao que importa, com o setup completo, os dois ícones distintos no Dock, e os gotchas que custaram caro pra eu descobrir.

Nota: Os scripts completos (rebuild.sh e invert.py) estão no repositório rflpazini/articles no GitHub. Os trechos aqui no artigo são exatamente o que está lá, copy-paste direto.

A ideia central, antes de descer no detalhe: o Claude Desktop é tipo o Notion ou o Slack, ele guarda todo o estado num diretório de dados (~/Library/Application Support/Claude). Se eu fizer o app apontar pra um diretório diferente, ele se comporta como uma instalação completamente nova. É o mesmo truque dos perfis do Chrome, só que o Claude não expõe isso na UI. Vamos forçar.


A decisão de escopo: só o Desktop

Antes de entrar na mecânica, vale explicar o que não foi separado.

A CLI continua única em ~/.claude/, com todos os skills, agents, memória de projeto e regras globais. É a CLI que importa pro trabalho real de desenvolvimento, e as duas contas compartilham esse contexto sem dor de cabeça.

O que não compartilham é a sessão do Desktop: preferências de UI, histórico de conversas, configurações de integrações, conectores. Cada instância tem seu próprio diretório de dados. Esse foi o escopo da separação, e funciona bem porque a "identidade" no Desktop é de uso, não de configuração técnica.


O layout final

/Applications/Claude.app           <- conta da empresa (original, intocado)
/Applications/Claude Personal.app  <- conta pessoal (cópia modificada)

~/Library/Application Support/Claude          <- dados da empresa
~/Library/Application Support/Claude-Personal <- dados pessoais

Enter fullscreen mode Exit fullscreen mode

Bundle IDs:

  • Empresa: com.anthropic.claudefordesktop (assinatura Anthropic Developer ID, notarized)
  • Pessoal: com.anthropic.claudefordesktop.personal (assinatura ad-hoc, sem notarização)

Ícone da conta pessoal: asterisco laranja em fundo cream (invertido). Permite distinguir as duas instâncias no Dock e no Cmd+Tab num piscar de olhos. Sem isso, vira loteria toda vez que você muda de janela.

Os dois ícones do Claude lado a lado no Dock do macOS

Resultado final no Dock: branco em laranja (empresa) à esquerda, laranja em cream (pessoal) à direita. Bate o olho e sabe qual é qual.


Como construir o Claude Personal.app

1. Copiar o app original

cp -R /Applications/Claude.app /Applications/"Claude Personal.app"

Enter fullscreen mode Exit fullscreen mode

Tranquilo, é só cp -R. A magia vem nos próximos passos.

2. Criar o wrapper de lançamento

O binário original do Electron precisa ser renomeado pra que o script wrapper ocupe o lugar dele:

cd "/Applications/Claude Personal.app/Contents/MacOS"
mv Claude Claude-bin

Enter fullscreen mode Exit fullscreen mode

Crie o wrapper Claude no lugar:

cat > "/Applications/Claude Personal.app/Contents/MacOS/Claude" << 'EOF'
#!/bin/bash
exec "$(dirname "$0")/Claude-bin" \
  --user-data-dir="$HOME/Library/Application Support/Claude-Personal" \
  "$@"
EOF
chmod +x "/Applications/Claude Personal.app/Contents/MacOS/Claude"

Enter fullscreen mode Exit fullscreen mode

O flag --user-data-dir é tudo que diferencia as duas instâncias. O Electron usa esse diretório pra sessão, preferências e cache. Dois diretórios diferentes, dois contextos completamente isolados. É o mesmo princípio que o --profile do Chrome usa, só que aqui a gente injeta na mão.

3. Alterar o bundle identifier no Info.plist

/usr/libexec/PlistBuddy \
  -c "Set :CFBundleIdentifier com.anthropic.claudefordesktop.personal" \
  "/Applications/Claude Personal.app/Contents/Info.plist"

Enter fullscreen mode Exit fullscreen mode

Gotcha: Esse aqui custou um crash inteiro pra eu descobrir. Altere somente CFBundleIdentifier e CFBundleDisplayName. Nunca toque em CFBundleName. O Electron resolve os processos auxiliares (Claude Helper.app, Claude Helper (GPU).app) pelo valor de CFBundleName. Se o nome não bater com o que está em Contents/Frameworks/, o app crasha com Unable to find helper app em electron_main_delegate_mac.mm:65 antes de mostrar qualquer janela. Você fica olhando pro Console do macOS sem entender. CFBundleDisplayName é o que aparece no Finder e no Dock, e é seguro alterar.

Pro display name (opcional, mas recomendado pra clareza):

/usr/libexec/PlistBuddy \
  -c "Set :CFBundleDisplayName 'Claude Personal'" \
  "/Applications/Claude Personal.app/Contents/Info.plist"

Enter fullscreen mode Exit fullscreen mode

4. Re-assinar o app

Modificar o binário e o Info.plist invalida a assinatura original. O macOS bloqueia o launch de apps com assinatura inválida (e com razão, é uma feature de segurança). Re-assine com ad-hoc:

# Remove atributos de quarentena antes de assinar
xattr -cr "/Applications/Claude Personal.app"

# Re-assina ad-hoc (sem certificado de desenvolvedor)
codesign --force --deep --sign - \
  "/Applications/Claude Personal.app"

Enter fullscreen mode Exit fullscreen mode

Gotcha: O xattr -cr antes do codesign é obrigatório, não decoração. O macOS adiciona o atributo com.apple.quarantine em qualquer app copiado ou baixado. Sem remover esse atributo, o codesign às vezes falha silenciosamente ou a Gatekeeper rejeita o app no primeiro launch. Você roda o app, ele não abre, e fica achando que o problema é outro.

O --sign - usa assinatura ad-hoc. O app não vai passar pelo processo de notarização da Apple, mas roda normalmente em Macs sem restrições de MDM que bloqueiem apps não notarizados. Pro seu Mac pessoal, suficiente.


O ícone invertido (e a armadilha do Asset Catalog)

Pra distinguir as instâncias visualmente, o ícone da conta pessoal tem as cores trocadas: asterisco laranja em fundo cream (ao invés do asterisco branco em fundo laranja original). É sutil, mas no Dock você bate o olho e sabe qual é qual.

A geração usa um script Python que percorre cada pixel e interpola entre as duas cores alvo proporcionalmente à distância de cada uma no espaço RGB. Esse approach preserva a textura granulada do ícone original (não chapa as cores) e mantém o alpha intacto:

Icones Claude

Esquerda: ícone original do Claude (asterisco branco em fundo laranja). Direita: a versão invertida que o invert.py gera. Mesma textura granulada, cores trocadas com gradiente proporcional.

# invert.py
import sys
import math
from PIL import Image

ORANGE = (216, 117, 85)
CREAM = (254, 252, 251)


def color_distance(a, b):
    return math.sqrt(sum((x - y) ** 2 for x, y in zip(a, b)))


def swap_pixel(rgba):
    r, g, b, a = rgba
    if a == 0:
        return rgba
    d_orange = color_distance((r, g, b), ORANGE)
    d_cream = color_distance((r, g, b), CREAM)
    total = d_orange + d_cream
    if total == 0:
        return rgba
    t = d_orange / total
    new_r = round((1 - t) * CREAM[0] + t * ORANGE[0])
    new_g = round((1 - t) * CREAM[1] + t * ORANGE[1])
    new_b = round((1 - t) * CREAM[2] + t * ORANGE[2])
    return (new_r, new_g, new_b, a)


def process(path_in, path_out):
    img = Image.open(path_in).convert("RGBA")
    pixels = list(img.getdata())
    swapped = [swap_pixel(p) for p in pixels]
    out = Image.new("RGBA", img.size)
    out.putdata(swapped)
    out.save(path_out, "PNG")


if __name__ == "__main__":
    process(sys.argv[1], sys.argv[2])

Enter fullscreen mode Exit fullscreen mode

A lógica do swap, em português claro: pra cada pixel, calcule a distância até ORANGE e até CREAM. Quanto mais próximo de ORANGE, mais peso a nova cor recebe de ORANGE (e o pixel "vira" laranja). O t = d_orange / total faz o blend contínuo: pixel exatamente igual a ORANGE vira CREAM (t=0, peso total no cream), pixel exatamente igual a CREAM vira ORANGE (t=1, peso total no orange), e tons intermediários ficam num gradiente proporcional. É inversão simétrica sem threshold, o que evita bandeamento (aquele efeito feio de "escadinha" entre cores).

Pra regenerar o .icns completo se a Anthropic mudar a paleta de cores um dia:

mkdir -p /tmp/ci
iconutil -c iconset /Applications/Claude.app/Contents/Resources/electron.icns \
  -o /tmp/ci/orig.iconset

mkdir -p /tmp/ci/new.iconset
for f in /tmp/ci/orig.iconset/*.png; do
  python3 ~/.claude/scripts/claude-personal-app/invert.py \
    "$f" \
    "/tmp/ci/new.iconset/$(basename $f)"
done

iconutil -c icns /tmp/ci/new.iconset \
  -o ~/.claude/scripts/claude-personal-app/personal.icns

Enter fullscreen mode Exit fullscreen mode

Depois de gerar o .icns, copie pro app:

cp ~/.claude/scripts/claude-personal-app/personal.icns \
  "/Applications/Claude Personal.app/Contents/Resources/electron.icns"

Enter fullscreen mode Exit fullscreen mode

E aqui chega o ponto que me fez perder umas 2 horas e quase mandar o MacBook pela janela.

Depois de trocar o .icns, re-assinar, limpar o cache de ícone do macOS, reiniciar o Dock, o Finder, matar o iconservicesd, sacrificar uma cabra ao deus da Apple... o Dock continuou mostrando os dois ícones idênticos. O arquivo .icns estava visivelmente diferente quando extraído com iconutil, mas o macOS insistia em renderizar o ícone original. Na cara dura.

A causa, depois de muito stack overflow e algumas blasfêmias: versões recentes do Claude Desktop (e de muitos apps Electron modernos) embutem o ícone num Assets.car (Asset Catalog do macOS), referenciado no Info.plist via CFBundleIconName. Quando essa chave existe, o macOS Big Sur+ prioriza o Asset Catalog sobre o CFBundleIconFile, e o .icns é completamente ignorado. Silenciosamente. Sem warning, sem nada.

Verifique se o seu app tem essa chave:

defaults read "/Applications/Claude Personal.app/Contents/Info.plist" CFBundleIconName
# Se retornar "Claude" ou similar, achamos o vilão

Enter fullscreen mode Exit fullscreen mode

Solução: remover CFBundleIconName força o fallback pro electron.icns.

/usr/libexec/PlistBuddy \
  -c "Delete :CFBundleIconName" \
  "/Applications/Claude Personal.app/Contents/Info.plist"

# Re-assinar depois de mexer no Info.plist
xattr -cr "/Applications/Claude Personal.app"
codesign --force --deep --sign - "/Applications/Claude Personal.app"

# Forçar o macOS a re-ler o ícone
touch "/Applications/Claude Personal.app"
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister \
  -f "/Applications/Claude Personal.app"
killall Dock Finder

Enter fullscreen mode Exit fullscreen mode

Gotcha: Se você mexer no ícone e nada mudar no Dock mesmo depois de limpar todos os caches conhecidos (com.apple.iconservices.store, iconservicesd, restart do Dock), procure por CFBundleIconName no Info.plist. Em apps Electron modernos é quase certo que essa chave está apontando pra um Asset Catalog que sobrescreve o .icns. Foi essa a sacada que me destravou, e eu não vi documentado em lugar nenhum. Anota aí.


Login inicial: o problema do deep link OAuth

Tem um detalhe que complica o primeiro login na conta pessoal e vale destacar antes que você quebre a cabeça.

O Claude usa OAuth com o esquema claude:// pro callback de autenticação. O macOS roteia esse deep link pra primeira instância registrada como handler desse esquema. Se as duas instâncias estiverem abertas, o callback vai pra conta da empresa (que registrou primeiro), e o login da conta pessoal nunca completa. Trava num loop.

O fluxo correto pro primeiro login:

1. Feche completamente o Claude.app (conta da empresa): Cmd+Q
2. Abra o Claude Personal.app
3. Complete o fluxo de login OAuth
4. Após logar, reabra o Claude.app (conta da empresa)

Enter fullscreen mode Exit fullscreen mode

Gotcha: Isso vale somente pro primeiro login. Depois que a sessão fica persistida no diretório Claude-Personal/, você pode abrir as duas instâncias ao mesmo tempo sem problema. O OAuth só é necessário uma vez por conta por diretório de dados. Se um dia a sessão expirar, o ritual do Cmd+Q precisa ser repetido.


Manutenção pós-update

O Claude Desktop tem auto-update via Squirrel. Quando sai uma versão nova, o updater substitui os arquivos dentro de /Applications/Claude.app/, preservando o app intocado.

O Claude Personal.app não recebe esse update automaticamente. Precisa ser recriado a partir da versão atualizada. É a parte chata, mas resolvível com script.

Pra isso, o rebuild.sh faz todo o processo de uma vez, incluindo a remoção do CFBundleIconName e o cache bust no final:

#!/bin/bash
# Rebuild Claude Personal.app from current Claude.app.
# Run this after Anthropic releases a new Claude version so Personal stays in sync.
set -euo pipefail

SRC="/Applications/Claude.app"
DST="/Applications/Claude Personal.app"
ICON="$(cd "$(dirname "$0")" && pwd)/personal.icns"
DATA_DIR_NAME="Claude-Personal"
NEW_BUNDLE_ID="com.anthropic.claudefordesktop.personal"
NEW_NAME="Claude Personal"

if [[ ! -d "$SRC" ]]; then
  echo "ERROR: $SRC not found." >&2
  exit 1
fi
if [[ ! -f "$ICON" ]]; then
  echo "ERROR: $ICON not found. Regenerate via invert.py." >&2
  exit 1
fi

if pgrep -f "Claude Personal.app" >/dev/null; then
  echo "Quit Claude Personal.app before rebuilding." >&2
  exit 1
fi

echo "Removing old $DST..."
rm -rf "$DST"

echo "Copying $SRC -> $DST..."
cp -R "$SRC" "$DST"

echo "Replacing icon..."
cp "$ICON" "$DST/Contents/Resources/electron.icns"

echo "Updating Info.plist..."
# IMPORTANT: do NOT change CFBundleName. Electron resolves helper apps by
# CFBundleName ("Claude Helper.app" etc). Changing it causes a FATAL crash:
# "Unable to find helper app". Use CFBundleDisplayName for the user-facing name.
/usr/libexec/PlistBuddy -c "Set :CFBundleDisplayName '$NEW_NAME'" "$DST/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier '$NEW_BUNDLE_ID'" "$DST/Contents/Info.plist"

# Drop CFBundleIconName so macOS falls back to electron.icns (the inverted one).
# Modern Electron apps embed an Asset Catalog (Assets.car) referenced by
# CFBundleIconName, and Big Sur+ prioritizes that over CFBundleIconFile.
# Without this delete, the .icns swap is silently ignored.
/usr/libexec/PlistBuddy -c "Delete :CFBundleIconName" "$DST/Contents/Info.plist" 2>/dev/null || true

echo "Installing wrapper binary..."
mv "$DST/Contents/MacOS/Claude" "$DST/Contents/MacOS/Claude-bin"
cat > "$DST/Contents/MacOS/Claude" <<EOF
#!/bin/bash
DIR="\$(cd "\$(dirname "\$0")" && pwd)"
exec "\$DIR/Claude-bin" --user-data-dir="\$HOME/Library/Application Support/$DATA_DIR_NAME" "\$@"
EOF
chmod +x "$DST/Contents/MacOS/Claude"

echo "Stripping xattrs and re-signing ad-hoc..."
xattr -cr "$DST"
codesign --force --deep --sign - "$DST"

echo "Refreshing Launch Services..."
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -f "$DST"
touch "$DST"
killall Dock Finder 2>/dev/null || true

mkdir -p "$HOME/Library/Application Support/$DATA_DIR_NAME"

echo
echo "Done. Open Claude Personal.app from Spotlight."
echo "Data dir: ~/Library/Application Support/$DATA_DIR_NAME"

Enter fullscreen mode Exit fullscreen mode

O rebuild apaga o .app atual e recria do zero. Sessão e configurações ficam intactas porque vivem no diretório de dados (~/Library/Application Support/Claude-Personal/), não dentro do bundle. Esse é o ponto crítico: o app é descartável, o estado é persistente.

A linha 2>/dev/null || true no Delete :CFBundleIconName é defensiva: se uma versão futura do Claude parar de embutir essa chave, o script continua funcionando sem alteração. Custa zero, salva o futuro.


Separar a CLI também (opcional)

Isso não foi necessário no meu caso, mas vale registrar pra quem quiser ir além.

Se um dia fizer sentido usar CLIs com configurações completamente diferentes por conta, basta apontar pra um diretório de configuração distinto:

# Em ~/.zshrc ou ~/.bashrc
alias claude-work='CLAUDE_CONFIG_DIR=$HOME/.claude-work claude'

Enter fullscreen mode Exit fullscreen mode

O ~/.claude/ continua sendo o diretório pessoal por padrão. O alias claude-work usa um diretório separado com suas próprias skills, agents e memória. Cada CLI roda completamente isolada da outra. É a mesma lógica do Desktop, mas com variável de ambiente em vez de wrapper script.


O que ficou no final

Claude personal e claude work abertos lado a lado

Dois ícones distintos no Dock: branco em laranja (empresa) e laranja em cream (pessoal). Acabou a loteria do Cmd+Tab.

As duas instâncias rodam ao mesmo tempo, sem conflito, com sessões totalmente isoladas. CLI compartilhada porque é lá que mora o trabalho real de desenvolvimento, com todos os skills, agents e contexto de projeto centralizado.

Quando sair update novo? Atualiza o Claude.app normalmente, roda rebuild.sh, 30 segundos, voltou pro ar. Workflow resolvido.

Os dois pontos de atenção, gravados a sangue depois dessa tarde:

  1. Nunca toque em CFBundleName. O Electron crasha antes de carregar a janela.
  2. Delete CFBundleIconName se quiser ícone customizado. O Asset Catalog ganha do .icns no Big Sur+, e ninguém te avisa.

O resto funciona sem segredo.

Se você usa duas contas do Claude (ou tá pensando em separar pessoal e trabalho em qualquer Electron app, porque o truque do --user-data-dir é genérico, vale pra Slack, Notion, VS Code), vale a hora de configuração. Me conta nos comentários se você usou em outro app, quero ver até onde dá pra adaptar o rebuild.sh. Bora trocar figurinha.