Claude Code headless: dá para rodar o agente dentro de um script, sem aprovar cada passo?

Claude Code headless é o agente rodando fora do terminal interativo: você passa claude -p "..." com o prompt, ele executa o loop até o fim, imprime o resultado e sai. Sem humano na frente, quem decide o que pode rodar é a configuração: --allowedTools pré-aprova ferramentas, --permission-mode fixa a linha de base da sessão e o modo dontAsk nega de saída tudo que abriria um prompt, posicionado pela doc justamente para CI e ambientes restritos. Some a isso --output-format, --max-turns, --bare e a leitura do código de saída (0 = sucesso) e o script anda sozinho sem virar aposta.
Fala aí, beleza? Agente de IA rodando sozinho dentro de um script é uma das coisas mais massa (e mais assustadoras) que dá pra montar hoje
No terminal a lógica é confortável: o Claude pede, tu aprova ou nega, a vida segue
Só que num pipeline de CI, num cron, num script em lote, não tem ninguém pra clicar em nada
E aí a pergunta muda de figura: não é mais "o agente consegue rodar sem interface?", é "o que exatamente ele tem permissão de fazer quando a resposta padrão não existe mais?"
Este post cobre as três partes disso: como rodar o Claude Code de forma não interativa, qual modo de permissão faz sentido quando ninguém está no terminal, e o que precisa estar decidido ANTES de tirar o humano do meio
Como rodar o Claude Code dentro de um script, passo a passo
A base é uma flag só, o resto é configuração em volta dela
- Rode com <code>-p</code> (ou <code>–print</code>) passando o prompt
Com essa flag o Claude Code executa o loop do agente até o fim, imprime o resultado e sai, sem interface de terminal
claude -p "Resuma as mudanças do último commit"
É o modo não interativo puro: entra prompt, sai texto, processo encerrado
- Escolha o formato de saída com <code>–output-format</code>
São três opções: <code>text</code>, <code>json</code> e <code>stream-json</code>
claude -p "Resuma as mudanças do último commit" --output-format json
O <code>stream-json</code> é JSON delimitado por linha, feito pra você processar em tempo real, conforme a execução acontece, em vez de esperar o fim
O erro comum deste passo: o script assumir que a saída é texto e tentar parsear como JSON (ou o contrário)
Decida o formato antes de escrever o parser, não depois
- Pré-aprove as ferramentas com <code>–allowedTools</code>
Essa flag usa a sintaxe de regras de permissão e serve exatamente pra execução não travar pedindo confirmação
claude -p "Find and fix the bug in auth.py" --allowedTools "Read,Edit,Bash"
O erro comum deste passo: esquecer uma ferramenta na lista
Num terminal isso viraria um prompt e você resolveria em dois segundos, num script vira uma execução que não sai do lugar
Domine o Claude Code do básico ao avançado
Você vai aprender a criar sistemas completos com Claude Code, sem precisar ser programador. Inscreva-se para ter acesso a um desconto de lançamento e bônus especiais!
- Fixe a linha de base com <code>–permission-mode</code>
O <code>–allowedTools</code> diz o que pode passar
A flag <code>–permission-mode</code> define o comportamento padrão da sessão inteira, ou seja, o que acontece com tudo que NÃO está na lista
claude -p "..." --allowedTools "Read,Edit" --permission-mode dontAsk
Os modos estão comparados na próxima seção
- Limite o gasto com <code>–max-turns</code>
Essa flag limita o número máximo de turnos que o agente pode dar numa execução (no Agent SDK, a opção equivalente é <code>max_turns</code>)
É o seu freio contra a execução que entra em loop de tentativa e fica ali, girando
- Ramifique o script pelo código de saída
O Claude Code sai com código 0 em caso de sucesso e código diferente de zero quando a execução falha
if claude -p "Rode os testes e corrija o que quebrou" --allowedTools "Read,Edit,Bash" --permission-mode dontAsk; then
echo "ok"
else
status=$?
if [ "$status" -eq 143 ]; then
echo "interrompido por SIGTERM"
else
echo "falhou com status $status"
fi
fi
O 143 tem nome e sobrenome: se a execução <code>claude -p</code> for interrompida com SIGTERM, o Claude Code aborta o turno em andamento, encerra a árvore de processos de qualquer comando Bash rodando, executa os hooks SessionEnd e sai com 143
O erro comum deste passo: tratar todo código diferente de zero como bug do agente
Interrupção do orquestrador não é bug 🙂
- Capture o id da sessão e use <code>–resume</code> quando o script toca várias conversas
claude -p "..." --resume <session-id>
Dá pra guardar o id da sessão e retomar aquela conversa específica depois, o que é o que segura um script em lote que precisa continuar de onde parou em vez de começar do zero a cada rodada
Modos de permissão do Claude Code: qual deixa o script andar sozinho
Aqui é o coração da coisa
O modo de permissão é o que define o comportamento quando aparece uma ação que ninguém pré-aprovou
| Modo | O que passa sem humano | O que continua barrado | Uso em pipeline |
|---|---|---|---|
| auto mode | Quem revisa a ação antes de executar não é você, é um segundo modelo classificador. É o modo padrão de novas sessões nos planos Pro, Max e Team desde 14 de agosto de 2026 | A revisão é do classificador, e a própria Anthropic diz que ele pega cerca de 83% dos comportamentos exagerados antes de rodarem | Serve como padrão do dia a dia, mas a decisão fica com o classificador, não com a sua allowlist |
| acceptEdits | Escreve arquivos sem perguntar e auto aprova comandos comuns de sistema de arquivos como <code>mkdir</code>, <code>touch</code>, <code>mv</code> e <code>cp</code> | Outros comandos de shell e requisições de rede continuam precisando de entrada em <code>–allowedTools</code> ou de uma regra <code>permissions.allow</code> | Bom quando o trabalho é editar arquivo no repositório e nada além disso |
| dontAsk | Só roda o que bate com <code>permissions.allow</code>, comandos Bash somente leitura e chamadas aprovadas por um hook PreToolUse | Nega automaticamente TODA chamada de ferramenta que normalmente abriria um prompt (disponível apenas na CLI, com <code>⏵⏵ don’t ask on</code> na barra de status) | É o modo que a doc posiciona pra CI e ambientes restritos, onde a sessão nunca espera input |
| bypassPermissions | Pula todos os prompts de aprovação | Nenhum prompt sobra, já que ele não pergunta nada | Trate como o modo sem ponto de parada declarado |
Repare na diferença de filosofia entre os dois extremos da tabela
O <code>dontAsk</code> é fail-closed por natureza: dúvida vira "não"
O <code>bypassPermissions</code> é o oposto: ele simplesmente não pergunta
E se a dúvida ainda for sobre o dia a dia interativo, quando aprovar e quando negar cada pedido do agente é uma conversa separada dessa aqui
Qual modo escolher quando ninguém está no terminal
Veredito direto: pra um agente travado, a combinação que a doc recomenda é lista de ferramentas permitidas MAIS <code>permissionMode: "dontAsk"</code>
O raciocínio é bonito de simples: o que está na lista é aprovado, o resto é negado de saída em vez de virar pergunta
Num script, pergunta sem destinatário é o mesmo que processo parado, então negar é literalmente mais útil do que perguntar
claude -p "Atualize o CHANGELOG com os commits desta branch" \
--allowedTools "Read,Edit" \
--permission-mode dontAsk
E o auto mode nessa história?
Ele tem um contexto numérico que vale conhecer: segundo a Anthropic, os usuários aceitam 93% dos prompts de permissão do Claude Code, e foi esse dado que motivou a criação do modo
Faz sentido, né? Se 93 de cada 100 pedidos terminam em "sim", o prompt vira ruído e o humano vira carimbo
Só que a mesma Anthropic diz que o auto mode pega cerca de 83% dos comportamentos exagerados do agente antes que eles rodem
Ou seja: é bom, é MUITO melhor que nada, e não é proteção total
Pra pipeline, isso importa: você troca uma allowlist que você escreveu (e consegue auditar) por um julgamento de modelo
Já o <code>bypassPermissions</code> deixa de ser atalho e vira aposta no momento em que o script roda em máquina com configuração herdada, mexe fora do diretório de trabalho ou toca rede
Nesse cenário, "pular todos os prompts" quer dizer que não sobra nenhum ponto de parada declarado
Se você quer fixar o padrão em vez de repetir flag em todo comando, dá pra usar o campo <code>defaultMode</code> dentro de <code>permissions</code> no arquivo de settings:
{
"permissions": {
"defaultMode": "acceptEdits"
}
}
Esse é o exemplo que a doc traz, com <code>acceptEdits</code> no valor, então não leia ele como recomendação pro seu caso
Pro cenário deste post o valor ali é outro: troque <code>acceptEdits</code> por <code>dontAsk</code> e o padrão da sessão passa a ser o modo que a gente escolheu na seção anterior, sem repetir flag em toda chamada
E tome cuidado com a hierarquia, porque ela decide quem ganha: enterprise settings vem primeiro, depois <code>.claude/settings.local.json</code> (local do projeto), depois <code>.claude/settings.json</code> (projeto) e por último <code>~/.claude/settings.json</code> (usuário)
Já me ferrei mais de uma vez achando que o arquivo de usuário mandava em tudo…
O que muda na segurança quando o humano sai do meio
Quando você aprova cada passo, você É a última linha de defesa
Tira o humano e essa linha passa a ser a configuração, só isso
A doc de segurança lista defesas do Claude Code contra injeção de prompt que continuam valendo:
- aprovação obrigatória por padrão para ferramentas que fazem requisições de rede
- janela de contexto isolada no web fetch
- detecção de injeção de comando: comando bash suspeito exige aprovação manual mesmo se já estava na allowlist
- matching fail-closed: comando não reconhecido cai em aprovação manual
Se liga no detalhe: várias dessas defesas terminam em "aprovação manual"
Num script, aprovação manual é exatamente o que não existe, então esses caminhos viram parada ou negação, dependendo do modo que você escolheu
No auto mode existe uma camada a mais: uma sonda do lado do servidor varre os resultados das ferramentas atrás de conteúdo suspeito, e o classificador de aprovação nunca vê os resultados das ferramentas, justamente pra que texto injetado não influencie a decisão dele
A confiança do diretório é o que liga o resto
O Claude Code mostra um diálogo de confiança do diretório antes de carregar a configuração
É essa confiança que libera a auto instalação de plugins de marketplace e a execução de hooks definidos pelo projeto, além de fazer valer o <code>.claude/settings.json</code> e o <code>.mcp.json</code>
A aceitação fica salva por diretório de projeto, com uma exceção: no diretório home ela vale só para a sessão atual
Traduzindo pro seu pipeline: confiar num diretório não é um clique bobo, é a chave que liga a configuração daquele projeto
<code>–bare</code> pra não herdar a máquina hospedeira
Em CI ou ambientes com script, a doc indica a flag <code>–bare</code> pra iniciar o Claude Code sem carregar hooks, plugins, auto memory e <code>CLAUDE.md</code> da máquina hospedeira
claude -p "..." --bare --output-format json
É o que separa "execução reproduzível" de "funciona na minha máquina"
Mas tem um efeito colateral que precisa entrar na conta, e é aqui que as duas partes do post conversam
Lembra que no <code>dontAsk</code> uma das coisas que passam são as chamadas aprovadas por um hook PreToolUse? Se você roda com <code>–bare</code>, os hooks não são carregados, então essa via de aprovação simplesmente não existe naquela execução
Sobram as outras duas: o que bate com <code>permissions.allow</code> e os comandos Bash somente leitura
Ou seja: ou você aceita a allowlist como único caminho de aprovação naquele pipeline, ou não usa <code>–bare</code> ali
Escolha consciente, não surpresa no meio da madrugada 😀
Sandbox de Bash: onde o comando pode escrever
O sandbox de Bash é embutido no Claude Code e roda em macOS, Linux e WSL2
No macOS não tem nada pra instalar, o isolamento usa o framework Seatbelt do sistema
Em Linux e WSL2 ele depende dos pacotes <code>bubblewrap</code> (isolamento de sistema de arquivos) e <code>socat</code> (relay de rede)
Windows nativo não é suportado, e a orientação é rodar dentro de uma distribuição WSL2
Com o sandbox ativo, por padrão os comandos não conseguem modificar arquivos fora do diretório de trabalho atual e do diretório temporário da sessão sem permissão explícita, o que inclui arquivos de configuração do shell como <code>~/.bashrc</code> e binários de sistema em <code>/bin</code>
E a doc é bem clara num ponto: sandbox eficaz exige isolamento de sistema de arquivos E de rede juntos, porque sem isolamento de rede um agente comprometido poderia exfiltrar arquivos sensíveis como chaves SSH
Isolamento de rede TLS-aware mais forte é área em desenvolvimento
Um último ponto pra quem administra time: nos planos Team e Enterprise o auto mode vem disponível por padrão, e o administrador pode desligar pra organização inteira nas managed settings, com <code>permissions.disableAutoMode</code> definido como <code>"disable"</code>
Onde rodar sem aprovação faz sentido (e onde não faz)
Bora aos casos concretos
CI e ambientes restritos É o cenário que a própria doc usa pra posicionar o <code>dontAsk</code>: sessão que nunca espera input, allowlist enxuta, tudo fora dela negado de saída
GitHub Actions A integração oficial é a action anthropics/claude-code-action, na versão <code>anthropics/claude-code-action@v1</code>, e ela roda no runner do próprio usuário
A autenticação aceita <code>ANTHROPIC_API_KEY</code> (chave da Claude Console) ou <code>CLAUDE_CODE_OAUTH_TOKEN</code> (token OAuth disponível nos planos Pro, Max, Team e Enterprise)
O caminho mais simples de setup é rodar <code>/install-github-app</code> dentro do Claude Code no terminal
E o app do GitHub precisa de permissões de leitura e escrita em Contents (pra modificar arquivos), Issues (pra responder issues) e Pull requests (pra criar PRs e enviar commits)
Scripts em lote Encadear sessões com <code>–resume</code> resolve o caso do processo que roda em várias etapas e precisa manter o fio da conversa entre elas
Aplicações usando o Agent SDK Ali existe o callback <code>canUseTool</code>, que dispara quando o Claude quer usar uma ferramenta que não foi aprovada automaticamente e devolve uma decisão programática:
// devolve allow ou deny de forma programática
{ behavior: "allow" }
{ behavior: "deny", message: "fora do escopo deste job" }
Detalhe importante da ordem: os hooks são avaliados ANTES do <code>canUseTool</code> e podem negar a chamada de saída
E onde não faz sentido?
Onde o script mexe fora do diretório de trabalho, toca rede ou roda numa máquina com configuração herdada de outro contexto
Nesses três, o custo de errar não compensa a comodidade de não ser perguntado
Se você ainda está decidindo qual agente usar em cada caso, essa conta de risco muda bastante de ferramenta pra ferramenta
Quando o script trava ou falha: sintomas e o que fazer
Sintoma: a execução fica parada e não sai nunca
Causa: alguma ferramenta ficou de fora da allowlist, e como uma execução <code>-p</code> não interativa não tem interface, não existe prompt pra onde escalar a decisão
Solução: <code>–allowedTools</code> mais <code>–permission-mode dontAsk</code>, ou então delegar a aprovação com <code>–permission-prompt-tool</code>
Prevenção: escreva a allowlist a partir do que o job REALMENTE faz, não do que ele talvez faça um dia
Sintoma: o agente se comporta diferente na máquina de CI
Causa: hooks, plugins, auto memory e <code>CLAUDE.md</code> da máquina hospedeira sendo carregados junto
Solução: <code>–bare</code>, lembrando que aí os hooks somem da equação e a allowlist fica sozinha no comando
Prevenção: trate o ambiente de CI como ambiente limpo por contrato, não por sorte
Sintoma: execução longa demais ou girando em loop
Causa: nada limita quantos turnos o agente pode dar
Solução: <code>–max-turns</code> mais ramificação por código de saída no script
Prevenção: teto de turnos definido junto com o prompt, não depois do primeiro susto
Sintoma: processo morto pelo orquestrador e o time achando que é bug
Causa: SIGTERM
Nesse caso o Claude Code aborta o turno em andamento, encerra a árvore de processos de qualquer comando Bash em execução, roda os hooks SessionEnd e sai com código 143
Solução: o script trata 143 como interrupção, não como falha do agente
Prevenção: documente o 143 no seu wrapper, senão alguém vai abrir issue por isso 😀
Sintoma: você precisa bloquear uma chamada específica que a allowlist não cobre bem
Causa: allowlist é lista, não é lógica
Solução: hook PreToolUse
Um hook que sai com código 2 bloqueia a chamada da ferramenta, e o texto escrito em <code>stderr</code> volta como feedback pro Claude
Pra controle mais fino, o hook sai com código 0 e imprime um JSON no <code>stdout</code> com a decisão (bloquear, permitir ou escalar)
Tome cuidado com dois detalhes: o Claude Code só processa o JSON quando o código de saída é 0 (JSON impresso junto com exit 2 não vira decisão estruturada), e hook nenhum vale se aquela execução subiu com <code>–bare</code>
Conclusão
Rodar o Claude Code headless não é decisão de coragem, é decisão de configuração
A sequência é essa: define a allowlist, escolhe o modo de permissão, isola o ambiente e trata o código de saída
O caminho conservador que a doc aponta pra agente travado continua sendo <code>–allowedTools</code> mais <code>dontAsk</code>, com <code>–bare</code> quando o ambiente não é seu (e ciente de que ali não tem hook pra te salvar) e sandbox quando o comando pode escrever onde não devia
Próximo passo prático: monte uma execução mínima num repositório de teste antes de plugar no pipeline de verdade
claude -p "Liste os arquivos alterados e explique o que mudou" \
--output-format json \
--allowedTools "Read" \
--permission-mode dontAsk \
--max-turns 5
Rodou, saiu JSON, exit 0? Então você já tem a peça que o script vai chamar, e pode ir soltando a coleira ferramenta por ferramenta, com consciência do que cada uma libera
Essa área muda rápido (modo padrão, sandbox, defesas), então aqui no Hora de Codar a gente vai voltando no assunto conforme a doc oficial mexer
até o próximo post!
Perguntas frequentes
Dá para integrar o Claude Code headless com o GitHub Actions?
Dá, a integração oficial é a action anthropics/claude-code-action@v1, que roda no runner do próprio usuário. A autenticação aceita ANTHROPIC_API_KEY (chave da Claude Console) ou CLAUDE_CODE_OAUTH_TOKEN (disponível nos planos Pro, Max, Team e Enterprise), e o caminho mais simples de setup é rodar /install-github-app dentro do Claude Code no terminal. O app do GitHub precisa de permissões de leitura e escrita em Contents, Issues e Pull requests.
O sandbox de Bash do Claude Code funciona em execução headless no Windows?
Não de forma nativa. O sandbox é embutido no Claude Code e roda em macOS, Linux e WSL2, e a orientação para quem está no Windows é rodar dentro de uma distribuição WSL2. No macOS o isolamento usa o Seatbelt do próprio sistema, sem nada pra instalar; em Linux e WSL2 ele depende dos pacotes bubblewrap e socat.
Como rodar o Claude Code em CI sem carregar hooks, plugins e CLAUDE.md da máquina?
Usando a flag –bare, indicada pela doc justamente para CI ou ambientes com script. Com ela o Claude Code inicia sem carregar os hooks, plugins, auto memory e CLAUDE.md da máquina hospedeira, ou seja, roda isolado do que está configurado localmente. Só lembre do efeito colateral: sem hooks carregados, um hook PreToolUse não aprova nada naquela execução, então no modo dontAsk sobram como caminho de liberação o que bate com permissions.allow e os comandos Bash somente leitura.
O que acontece se eu rodar claude -p sem configurar –permission-prompt-tool?
Existe a flag –permission-prompt-tool justamente pra delegar a decisão de aprovação a outro lugar. Numa execução -p não interativa sem essa flag configurada, não tem prompt para onde escalar a decisão, então quem monta o script precisa resolver isso via –allowedTools e –permission-mode antes de rodar, não durante.
Um hook PreToolUse consegue bloquear uma chamada mesmo com –allowedTools liberando ela?
Consegue. Um hook PreToolUse que sai com código 2 bloqueia a chamada da ferramenta, e o texto escrito em stderr volta como feedback pro Claude. Pra controle mais fino, o hook sai com código 0 e imprime um JSON no stdout com a decisão, mas o Claude Code só processa esse JSON quando o código de saída é 0. Vale o aviso: se aquela execução subiu com –bare, os hooks não são carregados, então esse controle não vale ali.
Dá para retomar uma execução do Claude Code headless que parou no meio do script?
Dá, capturando o id da sessão e usando –resume <session-id> na chamada seguinte. É o que segura um script em lote que precisa continuar de onde parou em vez de começar do zero a cada rodada, principalmente quando o script toca várias conversas diferentes.
Formações
Formação Vibe Coding
Do Prompt ao Produto: Crie Software Real com IA
- 474 aulas
- 20 projetos
- 39h 27min
Blog | Mais populares
As diferenças de var, let e const
Como fazer redirecionamento com PHP
Neste artigo você vai aprender a como fazer redirecionamento com PHP, utilizaremos abordagens fáceis de entender e de aplicar Fala programador(a), beleza? Bora aprender mais […]
Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação
Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação A popularidade da automação de processos com o n8n está em alta, principalmente […]
