Uso Claude Code em projetos de trabalho e em projetos pessoais, na mesma máquina. São contas diferentes. Por muito tempo isso significou deslogar e logar de novo toda vez que eu trocava de contexto — o que é exatamente o tipo de atrito que faz a gente parar de trocar de contexto.
Tentei resolver com uma extensão de VSCode que prometia perfis. Não funcionou, e o motivo do fracasso dela acabou sendo a parte mais interessante da história: eu estava errado sobre onde o Claude Code guarda a sua conta.
Por que a abordagem por extensão quebrou
A extensão fazia o óbvio: mantinha um diretório por perfil e trocava
~/.claude por um symlink apontando para o perfil ativo. O erro que eu
recebia era esse:
Failed to create symlink: EEXIST: file already exists,
symlink '/Users/voce/.claude-work' -> '/Users/voce/.claude'
O EEXIST é simples de ler quando você conhece a assinatura do Node:
a mensagem é symlink 'destino' -> 'caminho'. O caminho é
~/.claude, que já existia como diretório de verdade, cheio da minha
configuração. Não dá pra criar um symlink por cima.
Mas o problema de fundo era maior. Mesmo se o symlink funcionasse, não ia adiantar —
porque ~/.claude não é onde a conta mora.
Onde cada coisa realmente fica
O Claude Code espalha estado em três lugares, e confundir os três foi a raiz de toda a minha dificuldade:
~/.claude.json |
Config e estado. Tem oauthAccount (email e organização),
projects (permissões aprovadas, trust dialog, MCP por projeto)
e um monte de cache.
|
|---|---|
~/.claude/ |
Dados. projects/ com os transcripts das sessões,
plugins/, settings.json, memory.
|
| Keychain do macOS |
O token OAuth. Item Claude Code-credentials.
|
A extensão symlinkava só o segundo. Ela trocava meus plugins e meu histórico de lugar
e não encostava nem no .claude.json nem no Keychain. Ou seja: mesmo
funcionando perfeitamente, ela nunca teria trocado a conta.
Vale reforçar, porque é contraintuitivo: o oauthAccount dentro do
.claude.json é só metadado de exibição — o nome e o email que aparecem na
interface. Apagar aquilo não desloga nada. O token está no Keychain e sobrevive a
qualquer faxina que você faça nos arquivos.
A solução: CLAUDE_CONFIG_DIR
Não precisa de extensão nenhuma. O Claude Code tem uma variável de ambiente nativa,
CLAUDE_CONFIG_DIR, que realoca a raiz de configuração inteira. Dá pra
testar em trinta segundos num diretório descartável:
mkdir /tmp/cfgtest
CLAUDE_CONFIG_DIR=/tmp/cfgtest claude mcp list
# → "No MCP servers configured" (o meu normal lista 5)
# → e um .claude.json novo aparece dentro de /tmp/cfgtest
Isso confirma o isolamento: com a variável setada, ele não leu absolutamente nada do
meu ~. E o mais elegante é o que acontece no Keychain — o Claude deriva o
nome do item a partir do config dir. Depois de usar dois perfis, o meu tinha:
Claude Code-credentials ← ~/.claude (default)
Claude Code-credentials-a1b2c3d4 ← perfil 1
Claude Code-credentials-e5f6a7b8 ← perfil 2
Cada config dir ganha seu próprio item de credencial, com um sufixo de hash. Ou seja: o isolamento de conta é real, e é o próprio Claude Code que faz. Só precisei parar de brigar com symlink e usar a variável.
Os wrappers
No ~/.zshrc, uma função por perfil:
code-work() {
CLAUDE_CONFIG_DIR="$HOME/.claude-work" \
code -n --user-data-dir "$HOME/.vscode-work" "$@"
}
code-personal() {
CLAUDE_CONFIG_DIR="$HOME/.claude-personal" \
code -n --user-data-dir "$HOME/.vscode-personal" "$@"
}
Aí é code-work ~/Projects/algo-do-trabalho e pronto: a janela abre já
logada na conta certa.
O --user-data-dir não é decoração. O VSCode reusa um único
processo para todas as janelas. Sem essa flag, abrir um segundo projeto
aproveita o processo que já está rodando, e a variável de ambiente que você acabou de
setar simplesmente não chega lá — a janela nova herda o ambiente da primeira. O
--user-data-dir força um processo separado, e é isso que faz a variável
valer.
Por que a janela nova parece vazia
Ao abrir com um --user-data-dir novo, o VSCode aparece do jeito que veio de
fábrica: sem seus ajustes, layout no padrão, tudo estranho. A leitura natural é "perdi
minhas extensões". Mas não é isso que acontece.
--user-data-dir e --extensions-dir são flags
separadas. Você trocou só a primeira, então as extensões continuam
vindo de ~/.vscode/extensions, compartilhadas entre todos os perfis. Dá
para confirmar nos logs do perfil novo — a extensão do Claude está lá, carregada
normalmente.
O que o --user-data-dir isola de verdade:
- o
settings.jsondo VSCode (o do perfil novo nasce praticamente vazio) - keybindings e snippets
- layout, arquivos recentes, estado da UI
globalStorageeworkspaceStorage— o estado interno das extensões
Então as extensões estão instaladas e rodando, mas com o estado zerado. Se alguma delas guarda login próprio, vai pedir de novo. E as suas configurações do VSCode você copia uma vez:
cp ~/Library/Application\ Support/Code/User/settings.json \
~/.vscode-work/User/settings.json
Se quiser mesmo extensões separadas por perfil — instalar coisa de trabalho só no
perfil de trabalho — aí sim é --extensions-dir. Eu não quis: prefiro
manter uma instalação só e não ficar atualizando extensão em dois lugares.
Começando do zero (sem configurações)
Se a máquina é nova, ou se você ainda não tem nada configurado, é bem menos trabalho do que o resto deste post sugere. Crie os wrappers acima, abra cada perfil uma vez e faça login. Acabou.
Os diretórios se criam sozinhos, o Keychain ganha um item por perfil, e nunca vai existir estado compartilhado para desembaraçar depois. Se puder escolher, comece assim — a seção seguinte é só para quem já tem histórico acumulado num perfil único.
Já tenho projetos: migrando
Quem já usa Claude Code há um tempo tem tudo acumulado num perfil só — no meu caso, 22 projetos. Dividir isso tem duas partes, porque transcript e permissão moram em arquivos diferentes.
Vamos supor quatro projetos, dois de cada lado:
~/Projects/acme-api → trabalho
~/Projects/acme-web → trabalho
~/Projects/meu-blog → pessoal
~/Projects/jogo-2d → pessoal
1. Entenda o slug
Os transcripts ficam em ~/.claude/projects/, um diretório por projeto. O
nome do diretório é o path absoluto com /, . e _
trocados por -:
| Path do projeto | Diretório do transcript |
|---|---|
~/Projects/acme-api |
-Users-voce-Projects-acme-api |
~/Projects/ruby-3.4/loja |
-Users-voce-Projects-ruby-3-4-loja |
~/Projects/app_v2/core |
-Users-voce-Projects-app-v2-core |
Repare nas duas últimas linhas: o ponto do ruby-3.4 e o underscore do
app_v2 também viram traço. Se for automatizar, a regra é essa:
slug() { echo "$1" | sed 's/[\/._]/-/g'; }
slug "/Users/voce/Projects/acme-api"
# → -Users-voce-Projects-acme-api
2. Copie os transcripts
Com o slug em mãos, é cp. Copie, não mova — assim o
~/.claude original continua funcionando enquanto você testa:
mkdir -p ~/.claude-work/projects ~/.claude-personal/projects
cp -R ~/.claude/projects/-Users-voce-Projects-acme-api \
~/.claude/projects/-Users-voce-Projects-acme-web \
~/.claude-work/projects/
cp -R ~/.claude/projects/-Users-voce-Projects-meu-blog \
~/.claude/projects/-Users-voce-Projects-jogo-2d \
~/.claude-personal/projects/
3. Migre as permissões
Aqui está a parte que é fácil esquecer. Tudo que você já aprovou num projeto — os
allowedTools, o trust dialog, os servidores MCP — está na chave
projects do ~/.claude.json, indexada pelo path real do
projeto:
jq '.projects | keys[]' ~/.claude.json
# "/Users/voce/Projects/acme-api"
# "/Users/voce/Projects/acme-web"
# "/Users/voce/Projects/meu-blog"
# "/Users/voce/Projects/jogo-2d"
Sem migrar isso, o projeto abre no perfil novo e pede aprovação de tudo outra vez. A
ideia é gerar um .claude.json por perfil, cada um com o seu subconjunto.
Liste os paths de trabalho uma vez:
WORK=(
"/Users/voce/Projects/acme-api"
"/Users/voce/Projects/acme-web"
)
O perfil de trabalho fica com os que estão na lista. O --args passa os
paths para o jq, que os enxerga em $ARGS.positional:
jq '.projects |= with_entries(
select(.key as $k | $ARGS.positional | index($k)))' \
~/.claude.json --args "${WORK[@]}" \
> ~/.claude-work/.claude.json
O perfil pessoal fica com todo o resto — mesmo filtro, com | not no fim.
Assim você não precisa listar os pessoais, e nenhum projeto fica de fora por
esquecimento:
jq '.projects |= with_entries(
select(.key as $k | $ARGS.positional | index($k) | not))' \
~/.claude.json --args "${WORK[@]}" \
> ~/.claude-personal/.claude.json
Confira se a conta bate antes de seguir:
jq '.projects | length' ~/.claude-work/.claude.json # 2
jq '.projects | length' ~/.claude-personal/.claude.json # 2
jq '.projects | length' ~/.claude.json # 4, intacto
4. Plugins e settings
Por último, cada perfil precisa dos seus plugins e configurações:
for P in ~/.claude-work ~/.claude-personal; do
cp -R ~/.claude/plugins "$P/"
cp ~/.claude/settings.json "$P/"
done
Copiar deixa os perfis independentes: atualizar um plugin no trabalho não mexe no
pessoal. O custo é duplicar em disco e atualizar duas vezes. Se preferir o contrário,
ln -s ~/.claude/plugins resolve — aí é uma cópia só para os dois.
Repare que o ~/.claude original ficou intacto o tempo todo. Ele não
atrapalha em nada e serve de fallback enquanto você confirma que a divisão ficou certa.
Prós e contras
A favor
- Isolamento real, feito pelo próprio Claude Code — conta, histórico, permissões e MCP, tudo separado.
- Zero dependência de extensão de terceiro pra uma coisa que a ferramenta já resolve.
- Sem symlink global. A abordagem de trocar
~/.claudetem uma corrida embutida: duas janelas abertas, uma troca o link, e a outra passa a apontar pro perfil errado no meio da sessão. - Reversível. O
~/.claudecontinua lá; se der errado, é só parar de usar os wrappers.
Contra
- É por janela, não por workspace. Uma janela do VSCode = uma conta. Se você mistura projeto pessoal e de trabalho na mesma janela, isso não resolve.
- Precisa abrir pelo terminal. Clicar no ícone do VSCode te dá o perfil padrão, sempre.
- Configuração do VSCode duplicada, pelo motivo explicado acima.
- Plugins duplicados se você copiar em vez de symlinkar — no meu caso, uns 700 MB por perfil.
- Os itens de Keychain acumulam. Trocar o nome de um config dir gera hash novo, e o antigo fica pra trás.
Uma última pegadinha
Se você tem ANTHROPIC_API_KEY exportada no shell — coisa comum pra quem
mexe com a API ou com ferramentas tipo ai-memory — ela atropela tudo isso.
O Claude Code prioriza a API key sobre o OAuth, e aí os dois perfis autenticam pela
mesma chave, sem trocar conta nenhuma. Você monta o setup inteiro e ele parece
funcionar, mas está separando só o histórico.
Se precisar da variável pra outra coisa, escope ela onde é usada em vez de exportar global.
← Voltar para postagens