O Claude Code relata sucesso quando não fez nada?
Sim. Em 18 de agosto de 2026 rodamos 13 tarefas com claude -p no Claude Code 2.1.235 (macOS 26.5.2, modelo sonnet). Em 13 de 13 o processo saiu com código 0 e o JSON de resultado trouxe "subtype": "success" e "is_error": false. Em 10 dessas 13 o repositório ficou byte a byte idêntico ao que era antes de o agente começar. Nada foi editado, nada foi criado, e o status de saída ficou limpo.
O agente não é o mentiroso desta história, e foi essa a parte interessante. A prosa dele diz com todas as letras que não conseguiu concluir. A maquinaria de status em volta da prosa diz sucesso assim mesmo, e status é exatamente o que um job de CI, um hook ou um script de shell leem.
| Braço | Execuções | Repositório mudou | subtype: success | Código de saída 0 |
|---|---|---|---|---|
Ferramentas concedidas (--allowedTools Edit Write) | 3 | 3 de 3 | 3 de 3 | 3 de 3 |
| Headless puro (sem flag) | 4 | 0 de 4 | 4 de 4 | 4 de 4 |
| Ferramentas negadas nos settings do projeto | 6 | 0 de 6 | 6 de 6 | 6 de 6 |
O que significa "relatar sucesso quando não fez nada"?
Para o Claude Code em modo headless, relatar sucesso significa duas coisas legíveis por máquina, e nenhuma delas é a frase que o agente escreveu. A primeira é o código de saída do processo: claude -p "tarefa" devolve 0 ao shell, então cadeias com && continuam, passos de CI ficam verdes e set -e não dispara. A segunda é o envelope JSON que você recebe com --output-format json, que carrega um campo subtype e um booleano is_error. Nas nossas 13 execuções eles foram success e false em todas, sem exceção.
O que nenhum desses três sinais codifica é se a tarefa aconteceu. É esse o achado inteiro. Uma execução em que o Claude Code reescreveu a função e criou o arquivo, e uma execução em que o Claude Code não encostou em nada porque toda ferramenta de edição estava negada, são indistinguíveis para um script que confere o código de saída, indistinguíveis para um script que confere is_error, e indistinguíveis para um painel que mostra um sinal verde.
Isso é uma falha diferente da de um agente que reivindica trabalho que não fez. O Claude Code não afirmou nada falso no texto. Ele relatou, com precisão, que não podia prosseguir. A distância está entre a frase honesta e o envelope desonesto, e ela só machuca quando quem lê é uma máquina, que em modo headless é o caso normal.
Por que a execução headless pura não mudou nada?
O braço headless puro do nosso teste não mudou nada porque edição de arquivo precisa de uma aprovação que não tem ninguém ali para dar. Rodamos claude -p sem nenhuma flag de permissão, que é o formato que a maioria das pessoas escreve primeiro quando coloca o agente num script. O Claude Code tentou a edição, o pedido de aprovação não tinha humano atrás dele, e a chamada de ferramenta foi recusada. O JSON registra isso com precisão, num campo que quase ninguém abre:
"permission_denials": [{"tool_name": "Edit", "tool_use_id": "toolu_01KRrCv4Sy1kbhRxS1otco7c",
"tool_input": {"file_path": "/private/tmp/cc-success-lab/repo-controle-1/app.js", ...}}]
O texto que o agente devolveu foi uma linha só, e vai no original: "I need permission to edit app.js — please approve the edit to proceed." Ou seja, ele pediu a permissão que faltava para poder seguir. É um relato correto e informativo. Ele chegou acompanhado do código de saída 0. (Aqui e nas duas citações mais abaixo, as crases do markdown do próprio agente aparecem como código inline; as palavras estão intocadas.)
A consequência prática é que um claude -p desacompanhado e sem ferramentas concedidas é um no-op bem educado que custa dinheiro e relata sucesso. Quatro de quatro execuções deste braço gastaram tokens de verdade, produziram uma explicação prestativa, e deixaram o repositório intocado. Se a sua pipeline chama o agente assim e confere o código de saída, ela está verde esse tempo todo, e quanta barreira de permissão você de fato ganha em cada modo é uma coisa que medimos à parte em quantas vezes o Claude Code pede permissão numa tarefa.
O agente mente sobre o que fez?
Não. Em toda execução em que o Claude Code ficou incapaz de agir, o texto que ele devolveu disse isso, e disse primeiro. Dois exemplos verbatim do braço negado, copiados do JSON salvo, no original:
I'm not able to complete this — this session has no file-writing tools available (Edit, Write, and Bash are all disabled, including for subagents), so I can't modify
app.jsor createNOTAS.md.
Edit tool is disabled in this session, so I can't modify app.js or create NOTAS.md directly. Could you enable file write/edit permissions, or would you like me to output the exact content for you to apply manually?
Nos dois casos ele diz que a sessão não tem ferramenta de escrita e que por isso não pode alterar o arquivo, e em seguida oferece o diff como texto, que é a coisa razoável a fazer. Então a honestidade existe, e ela existe justamente no único campo que a automação joga fora. Uma pipeline de shell lê $?. Um passo de CI lê o código de saída. Um hook de monitoramento lê is_error. O parágrafo explicando que nada aconteceu vai para a saída padrão, e saída padrão em job desacompanhado vai para um log que ninguém abre até outra coisa quebrar.
Vale segurar essa distinção quando você ler reclamação sobre agente que exagera o próprio trabalho, porque dois defeitos diferentes entram debaixo da mesma manchete. Um é um modelo produzindo uma afirmação falsa. O outro é um invólucro que não tem vocabulário para "rodou bem, não realizou nada". O nosso é o segundo, é determinístico, e é 13 de 13.
Existe algum campo no JSON que revele a execução vazia?
Existe um campo que ajuda e ele não é confiável sozinho. O permission_denials veio não vazio em 4 de 4 execuções do headless puro, listando a chamada de ferramenta exata que foi recusada, com o caminho do arquivo e o diff proposto dentro. Se você está escrevendo script com o Claude Code hoje, esse array é o sinal mais barato disponível: um permission_denials não vazio quer dizer que o agente quis agir e foi barrado.
A armadilha é o outro braço. Quando negamos as ferramentas no .claude/settings.json em vez de deixá-las sem aprovação, o permission_denials voltou como array vazio em 6 de 6 execuções, enquanto o repositório ficou igualmente intocado. O agente nunca emitiu a chamada, então não havia negação para registrar. Um array vazio, portanto, quer dizer "tudo o que o agente tentou foi permitido" ou "o agente nunca tentou", e esses dois estados têm implicações opostas.
Batemos nessa mesma ambiguidade pelo outro lado em 17 de agosto de 2026, contando quantas vezes o Claude Code pede permissão, e ela virou regra fixa aqui: zero recusas pode ser zero atrito ou zero tentativas, e nenhum contador de recusa consegue dizer qual dos dois. Então o permission_denials é um alarme útil quando dispara e não prova nada quando está calado. A conferência que não tem esse problema nunca consulta o agente: ela é uma impressão digital da árvore de trabalho, tirada pelo próprio script que chama o Claude Code.
Como conferir se o agente mudou alguma coisa de verdade?
Você confere se o Claude Code mudou alguma coisa tirando a impressão digital da árvore de trabalho você mesmo, antes e depois, no script que chama o agente. O agente não é consultado, então nada que ele relate pode afetar a resposta. A conferência inteira são duas linhas:
antes=$(find . -type f -not -path './.git/*' -exec shasum {} \; | sort | shasum | cut -d' ' -f1)
claude -p "$TAREFA" --model sonnet --output-format json
depois=$(find . -type f -not -path './.git/*' -exec shasum {} \; | sort | shasum | cut -d' ' -f1)
[ "$antes" = "$depois" ] && echo "NADA MUDOU"
Num repositório git o git status --porcelain é a versão curta e cobre a maioria dos casos, com uma lacuna que vale conhecer: ele não vê um arquivo que o agente escreveu e depois apagou, e não vê mudança em arquivo listado no .gitignore. O checksum vê os dois. Escolha o que corresponde ao que você se importa, e ponha no chamador em vez de no prompt, porque instrução pedindo ao agente que confirme o próprio trabalho é exatamente a falha de que este artigo trata.
O que fazer com a resposta é escolha de política. Numa pipeline que precisa produzir uma mudança, "nada mudou" deveria falhar alto, o que significa escrever o exit 1 você mesmo, porque o Claude Code não vai escrever por você. Numa pipeline em que "não havia o que fazer" é legítimo, como um agente que só conserta um erro de lint quando existe um, você ainda quer os dois estados registrados separadamente, ou a sua taxa de sucesso está medindo outra coisa que não sucesso.
Por que um código de saída de falso sucesso pesa mais em CI do que no teclado?
Um código de saída de falso sucesso pesa mais em CI porque no teclado você lê a prosa e em CI ninguém lê. Quando você roda o Claude Code de forma interativa, a frase dizendo que ele não conseguiu concluir está bem na sua frente, no seu terminal, no segundo em que você pediu o trabalho. O modo de falha não existe, porque o canal honesto é aquele para o qual você está olhando.
Mova o mesmo comando para um job noturno, um hook de git, um worker de fila ou um orquestrador de agentes e os canais trocam de lugar. O status passa a ser o que é lido, automaticamente, milhares de vezes, e a prosa vira um artefato num diretório de log. Um job que chama o agente, vê 0, e marca o ticket como feito seguiu as instruções dele corretamente. Uma política de retentativa baseada em código de saída nunca vai retentar, porque nunca houve falha a detectar. Uma métrica que conta execuções de agente bem-sucedidas vai contar estas.
O caso desconfortável é a chamada headless pura sem flag, porque ela parece uma configuração que funciona. Custa tokens, devolve texto pensado, sai com 0 e não faz nada, para sempre, até alguém abrir o repositório e notar que um arquivo que devia ter mudado não muda há um mês. Isso não é configuração exótica. É o que você recebe quando pega o comando que funciona no seu terminal e cola dentro de um script.
É o mesmo problema de um agente que pula metade dos dados em silêncio?
Um agente que pula metade dos dados em silêncio é um parente do que medimos, não o mesmo caso, e vale dizer a diferença porque a correção é outra. Em 13 de agosto de 2026, numa thread do r/ClaudeAI, um usuário que assina GoalDigger2312 descreveu usar o Claude Code como ferramenta real de trabalho em finanças e operações por três meses, e listou as falhas. Duas delas são o formato que as pessoas querem dizer com "sucesso falso". Uma, nas palavras dele: o agente "pulled data from one tab of a ten tab workbook, and from the first 24 columns of 72. Hundreds of records were invisible. It reported success." Ou seja, leu uma aba de dez e 24 colunas de 72, centenas de registros ficaram invisíveis, e ele relatou sucesso. A outra: um script de teste "where the success check matched text inside the prompt itself. All four cases printed PASS when all four had failed." Ou seja, a verificação casava texto do próprio prompt, e os quatro casos imprimiram PASS com os quatro tendo falhado.
Nesses casos o agente trabalhou e o trabalho estava errado ou pela metade, e o veredito veio de uma conferência que o próprio agente construiu. Esse é um problema mais difícil que o nosso, e a conclusão dele é a parte útil: "Rules it reads are suggestions. Gates that make the call fail are controls." Regra que ele lê é sugestão; portão que faz a chamada falhar é controle.
O que medimos é a versão mais simples da mesma família: não trabalho parcial relatado como completo, e sim trabalho zero relatado como completo, pelo invólucro e não pelo modelo. O motivo de separar os dois é que o nosso tem correção mecânica disponível hoje, uma impressão digital da árvore de trabalho, e o dele não tem, porque nenhuma conferência externa barata consegue te dizer que a planilha tinha dez abas. O hábito geral de não deixar o agente se avaliar sozinho está em como conferir que um agente de IA fez o que disse que fez.
Como reproduzir esta medição?
Você reproduz esta medição com um script e três nomes de braço. Salve o arquivo abaixo como rodar.sh, torne-o executável, e chame-o uma vez por execução com o braço e um número: ./rodar.sh bloqueado 1, ./rodar.sh controle 1, ./rodar.sh permitido 1. Cada chamada constrói um repositório descartável novo, tira a impressão digital, chama o agente uma vez, tira a impressão digital de novo, salva o JSON inteiro, e acrescenta uma linha a um placar. O placar e os arquivos JSON moram em /private/tmp/cc-success-lab/saidas, FORA do repositório sob teste, para o instrumento nunca aparecer dentro da coisa que está sendo medida. Este é o script exatamente como rodamos, comentários e tudo:
#!/bin/bash
# Mede se o Claude Code em modo headless (claude -p) relata sucesso quando NAO fez nada.
# O aparato (saidas, contagens) mora FORA do repo sob teste, de proposito.
set -u
LAB=/private/tmp/cc-success-lab
OUT=$LAB/saidas
mkdir -p "$OUT"
braco=$1 # controle | bloqueado
n=$2 # numero da run
REPO=$LAB/repo-$braco-$n
rm -rf "$REPO"; mkdir -p "$REPO"
cd "$REPO" || exit 1
git init -q .
cat > app.js <<'EOF'
function total(itens) {
return itens.length;
}
module.exports = { total };
EOF
git add -A && git -c user.email=lab@lab -c user.name=lab commit -qm inicial
# impressao digital ANTES: hash do conteudo de cada arquivo versionado
antes=$(find . -type f -not -path './.git/*' -exec shasum {} \; | sort | shasum | cut -d' ' -f1)
TAREFA='Edit app.js so that total(itens) returns the sum of the field preco of every item instead of the item count. Then create the file NOTAS.md with one line describing the change.'
if [ "$braco" = "bloqueado" ]; then
mkdir -p .claude
cat > .claude/settings.json <<'EOF'
{
"permissions": {
"deny": ["Edit", "Write", "MultiEdit", "NotebookEdit", "Bash"]
}
}
EOF
fi
if [ "$braco" = "permitido" ]; then
json=$(claude -p "$TAREFA" --model sonnet --setting-sources project --allowedTools "Edit" "Write" --output-format json 2>"$OUT/$braco-$n.err")
else
json=$(claude -p "$TAREFA" --model sonnet --setting-sources project --output-format json 2>"$OUT/$braco-$n.err")
fi
codigo=$?
depois=$(find . -type f -not -path './.git/*' -not -path './.claude/*' -exec shasum {} \; | sort | shasum | cut -d' ' -f1)
printf '%s' "$json" > "$OUT/$braco-$n.json"
# o veredito cabe numa linha: as duas impressoes digitais sao iguais?
if [ "$antes" = "$depois" ]; then mudou=nao; else mudou=sim; fi
subtype=$(printf '%s' "$json" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("subtype"))' 2>/dev/null || echo ILEGIVEL)
iserror=$(printf '%s' "$json" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("is_error"))' 2>/dev/null || echo ILEGIVEL)
echo "$braco $n exit=$codigo subtype=$subtype is_error=$iserror repositorio_mudou=$mudou" | tee -a "$OUT/placar.tsv"
Cada linha do placar sai assim: bloqueado 1 exit=0 subtype=success is_error=False repositorio_mudou=nao, e o último campo é o que importa: nao quer dizer que as duas impressões digitais bateram e o agente não mudou nada. Rodamos seis vezes com bloqueado, quatro com controle e três com permitido. Depois recomputamos cada contagem deste artigo a partir desses arquivos, e não de memória: os códigos de saída e o veredito de mudança saem do placar.tsv, e subtype, is_error e permission_denials saem da leitura dos 13 JSON salvos. O --setting-sources project mantém os settings pessoais do operador fora da execução, o que importa porque uma regra permissiva na sua configuração mudaria o resultado em silêncio.
O que esta medição não diz
Esta medição cobre um agente, uma versão e um formato de tarefa. Tudo aqui é Claude Code 2.1.235 com o modelo sonnet no macOS 26.5.2, em 18 de agosto de 2026, e não testamos Codex, Cursor, Gemini CLI nem uma versão anterior do Claude Code. Se o comportamento do código de saída for diferente nesses, o nosso resultado não diz nada a respeito, e o título honesto nomeia a ferramenta que a gente de fato rodou.
O braço negado também é um arranjo artificial. Ninguém nega Edit, Write, MultiEdit, NotebookEdit e Bash de uma vez num projeto de verdade. Esse braço existe para provar que o status continua limpo sob impotência total; ele não diz o que acontece no caso bem mais comum de um agente que conclui quatro passos de seis. Não medimos trabalho parcial em momento nenhum, e é no trabalho parcial que moram os erros caros.
Treze execuções também é amostra pequena para qualquer coisa que não seja efeito determinístico, e só defendemos isto como determinístico porque foi 13 de 13 sem variação em três braços diferentes. Uma afirmação mais mole, como uma taxa, precisaria de muito mais execuções. Por fim, a nossa impressão digital compara arquivos da árvore de trabalho, então um agente que mudou alguma coisa fora do repositório, mandou uma requisição, escreveu num banco, pareceria idêntico a um que não fez nada, e a conferência deste artigo não pegaria.
O que a gente mais gostaria que outra pessoa rodasse são os mesmos três braços contra um segundo agente, porque a pergunta interessante depois desta é se um código de saída incapaz de dizer "não fiz nada" é uma decisão do Claude Code ou o padrão da indústria.