Hooks são regras de automação por evento. Quando algo acontece na sessão (o agente salva um arquivo, vai chamar uma ferramenta, conclui uma tarefa da spec), o Kiro roda um comando de shell ou injeta uma instrução no agente. No Kiro, a IA agêntica da AWS para desenvolvimento de software, cada hook é definido em JSON dentro de .kiro/hooks/ e versionado junto com o código. Um hook de comando em PreToolUse que sai com código 2 bloqueia a chamada da ferramenta e devolve o STDERR ao agente.
Verificado contra a documentação de hooks do Kiro na atualização de 15/10/2026. Gatilhos, campos do payload e comportamento de bloqueio podem mudar entre versões da IDE e da CLI: teste cada hook na versão instalada antes de depender dele.
Na arquitetura de um repositório preparado para agente, cada peça tem um papel. O steering registra as convenções que o agente deve seguir. A spec transforma o requisito em plano e tarefas revisáveis. Os hooks fecham o ciclo com verificação: rodam o lint, os testes e as checagens de segurança em pontos fixos do fluxo, com o mesmo resultado para qualquer pessoa do time. Se você está chegando agora, o panorama do Kiro explica as superfícies e o modelo de adoção.
Steering orienta e pode ser interpretado. Hook de comando executa e devolve código de saída. É essa diferença que torna o hook a camada certa para tudo que tem resposta objetiva.
Os hooks orientados a evento, com ação de comando ou de agente, estão disponíveis na IDE e no CLI, segundo a documentação de hooks do Kiro. Sessões Web guardam a configuração para uso local, mas não executam nenhum gatilho, conforme a página de tipos de hook.
Formato do arquivo
Cada arquivo .json em .kiro/hooks/ declara um ou mais hooks. Qualquer nome de arquivo funciona; a documentação sugere kebab-case descritivo, como lint-on-save.json. Os hooks são ativados automaticamente quando a sessão começa, sem registro manual.
Campos obrigatórios:
version: versão do esquema, atualmente"v1".hooks: lista de definições de hook.hooks[].name: identificador legível.hooks[].trigger: nome do evento em PascalCase, comoPostFileSaveouPreToolUse.hooks[].action: objeto comtypeigual a"command"(exige o campocommand) ou"agent"(exige o campoprompt).
Campos opcionais:
description: só documentação, sem efeito na execução.matcher: filtro do evento. Em gatilhos de ferramenta, seleciona a ferramenta; em gatilhos de arquivo, é uma regex sobre o caminho. Sem matcher, o hook casa com tudo.timeout: limite em segundos para ações de comando. O padrão é 60; o valor0desativa o limite. É ignorado em ações de agente.enabled:falsedesliga o hook sem apagar o arquivo.confirm: pede confirmação antes de um hook de comando no gatilhoStop, com pergunta e opções de botão.
O exemplo oficial roda o ESLint quando o agente salva um arquivo TypeScript:
{
"version": "v1",
"hooks": [{
"name": "Lint on save",
"trigger": "PostFileSave",
"matcher": "\\.(ts|tsx)$",
"action": { "type": "command", "command": "npx eslint --fix" }
}]
}
No Kiro CLI V3, o diretório ~/.kiro/hooks/ guarda hooks globais, que valem para todos os workspaces e rodam lado a lado com os do projeto. O formato .kiro/hooks/*.json chegou com a IDE 1.0 e o CLI V3: quem vem da IDE 0.x migra para arquivos avulsos com gatilhos em PascalCase, e quem vem do CLI 2.x roda /upgrade-agent para converter hooks embutidos no perfil do agente, como descreve a página de migração de hooks do CLI V3.
Os gatilhos disponíveis
A tabela abaixo cruza o nome de cada gatilho na documentação, o identificador usado no campo trigger e a superfície onde ele roda. Os identificadores vêm do mapeamento de gatilhos da IDE 1.0 e da página de migração do CLI V3. A coluna de bloqueio também vem dessas duas páginas: o guia da IDE 1.0 marca como bloqueantes PreToolUse, PreTaskExec e UserPromptSubmit, e a página do CLI V3 informa que o prompt é enviado qualquer que seja o código de saída.
| Gatilho | No JSON | IDE | CLI | Pode bloquear |
|---|---|---|---|---|
| Prompt Submit | UserPromptSubmit | Sim | Sim | Só na IDE |
| Agent Stop | Stop | Sim | Sim | Não; no CLI V3 pode solicitar mais um turno |
| Session Start | SessionStart | Sim | Só V3 | Não |
| Session End | SessionEnd | Não | Só V3 | Não |
| Pre Tool Use | PreToolUse | Sim | Sim | Sim |
| Post Tool Use | PostToolUse | Sim | Sim | Não |
| File Create | PostFileCreate | Sim | Só V3 | Não |
| File Save | PostFileSave | Sim | Só V3 | Não |
| File Delete | PostFileDelete | Sim | Só V3 | Não |
| Pre Task Execution | PreTaskExec | Sim | Só V3 | Sim |
| Post Task Execution | PostTaskExec | Sim | Só V3 | Não |
| Manual | listado no CLI V3 | Não | Listado, sem execução pelo /hooks | Não se aplica |
| Hook manual legado | IDE 0.x | Só os já existentes | Não | Não se aplica |
Quatro detalhes da documentação mudam o desenho dos hooks:
- Gatilhos de arquivo respondem só ao agente. File Create, File Save e File Delete disparam para mudanças feitas pelo agente; edição manual no editor não dispara.
- Prompt Submit expõe o texto. Ações de comando leem o prompt na variável de ambiente
USER_PROMPT. - Pre e Post Task Execution são ligados às tarefas da spec. Disparam antes e depois da execução de uma tarefa, o que os torna o ponto natural de verificação no fluxo de spec-driven development.
- Fluxo sob demanda virou steering. Hooks manuais da IDE 0.x continuam executáveis, mas não é possível criar novos. Para uma rotina acionada por você, a documentação indica um arquivo de steering com inclusão manual, chamado como comando
/<nome-do-arquivo>.
Ação command ou ação agent
A escolha da ação define se o hook é verificação ou orientação.
Ação command. Roda um comando de shell na raiz do projeto e recebe o contexto da sessão em JSON pelo STDIN. Para ferramentas, a documentação lista no payload o nome do evento, o diretório de trabalho, o ID da sessão, o nome da ferramenta e a entrada da ferramenta. O código de saída decide o efeito, conforme as ações de hook:
- Saída 0: sucesso; o STDOUT entra no contexto do agente.
- Saída 2 em
PreToolUse: bloqueia a execução da ferramenta, e o STDERR volta para o agente como explicação, segundo a referência de códigos de saída. - Saída 2 em
PreTaskExeceUserPromptSubmit: o guia de hooks da IDE 1.0 também lista esses dois gatilhos como bloqueantes. No CLI V3, a página de migração confirma o bloqueio emPreTaskExece informa que oUserPromptSubmitnão bloqueia. - Outro código: erro do hook, tratado como falha e não como bloqueio.
A referência de códigos de saída lista o bloqueio apenas em PreToolUse. Antes de apoiar uma regra do time no bloqueio de PreTaskExec ou de UserPromptSubmit, a recomendação da RFX é validar o comportamento no próprio ambiente com um teste simples: um hook que sai sempre com 2 e uma tarefa ou um prompt de teste.
Ações de comando rodam localmente, costumam ser mais rápidas e não consomem créditos, segundo as boas práticas de hooks.
Ação agent. Injeta um prompt na conversa, e o agente age sobre ele como se você tivesse digitado no chat. No gatilho Prompt Submit, o texto do hook é anexado ao prompt do usuário. Cada disparo inicia um novo ciclo do agente e consome créditos.
O critério que uso para decidir é simples: se um programa consegue dizer "passou" ou "não passou", o hook é de comando. Se a checagem exige leitura de contexto e julgamento, o hook é de agente. Lint, tipos, testes, segredos e comandos proibidos são comando. Revisão de clareza de uma mudança, coerência com o design da spec e sugestão de teste faltante são agente.
{
"version": "v1",
"hooks": [
{
"name": "revisao-de-design",
"description": "Orientação: confere a mudança contra o design da spec",
"trigger": "PostTaskExec",
"action": {
"type": "agent",
"prompt": "Releia o design.md da spec ativa. Liste, em no máximo 5 itens, divergências entre o que a tarefa concluída implementou e o design aprovado. Se não houver divergência, responda apenas: design respeitado."
}
}
]
}
Repare no formato do prompt: uma tarefa só, limite de saída e resposta curta quando está tudo certo. As boas práticas oficiais pedem exatamente isso: instrução específica, um objetivo por hook e passos numerados quando a tarefa é complexa.
Matcher: categorias, seletores e regex
Em PreToolUse e PostToolUse, o matcher escolhe quais chamadas de ferramenta disparam o hook. A sintaxe documentada aceita várias entradas:
- Categorias nativas:
read,write,shell,webespec. - Seletores de origem:
@mcp(todas as ferramentas MCP),@powers(todas as ferramentas de Powers),@builtin(todas as nativas),@<servidor>(todas as ferramentas de um servidor MCP) e@<servidor>/<ferramenta>(uma ferramenta específica). - Regex: o editor da IDE trata cada entrada como regex sobre o ID da ferramenta. Uma palavra simples como
readcasa com qualquer ID que contenha esse texto. - Curingas:
*e?funcionam no JSON, mas o editor da IDE os rejeita; para usá-los, edite o arquivo diretamente. - Campo vazio: casa com todas as ferramentas.
Um detalhe que pega quem escreve script: nos eventos do V3, ferramentas MCP aparecem no payload com o ID interno sanitizado mcp_<servidor>_<ferramenta>, e não com o nome de exibição @servidor/ferramenta. O matcher usa o seletor; o script que lê o STDIN recebe o ID interno. Nos gatilhos de arquivo, o matcher é uma regex sobre o caminho do arquivo. Nos demais gatilhos, ele não é avaliado.
Antes de escrever qualquer script de verificação, capture um payload real. A documentação não publica o esquema de tool_input de cada ferramenta, e adivinhar o nome de um campo é a forma mais rápida de escrever um hook que nunca bloqueia nada. Uma pista inicial está no Baseline de Segurança DevSecOps para o Kiro, do blog da AWS Brasil, que testa os guards com tool_input.command para shell e tool_input.path para escrita. Confirme os dois na sua versão com um hook de captura:
{
"version": "v1",
"hooks": [
{
"name": "capturar-payload",
"description": "Diagnóstico: grava o STDIN do evento para inspeção. Remover depois.",
"trigger": "PreToolUse",
"matcher": "shell",
"action": { "type": "command", "command": "cat > /tmp/kiro-payload-shell.json" },
"timeout": 5
}
]
}
O payload pode conter caminhos, comandos e trechos de dados. Inspecione, anote os campos e apague o arquivo e o hook no mesmo dia.
Cinco hooks de referência
Os cinco hooks a seguir cobrem os pontos de verificação que mais aparecem em repositórios preparados para agente. Os scripts ficam em tools/kiro-hooks/, fora de .kiro/hooks/, para que o diretório de hooks contenha só as definições. Todos os comandos rodam a partir da raiz do projeto, então caminhos relativos funcionam.
1. Lint e tipos quando o agente salva um arquivo
{
"version": "v1",
"hooks": [
{
"name": "lint-ao-salvar",
"description": "ESLint com correção automática em TypeScript salvo pelo agente",
"trigger": "PostFileSave",
"matcher": "\\.(ts|tsx)$",
"action": { "type": "command", "command": "npx eslint --cache --fix 1>&2" },
"timeout": 60
},
{
"name": "tipos-ao-salvar",
"description": "Checagem de tipos sem gerar saída",
"trigger": "PostFileSave",
"matcher": "\\.(ts|tsx)$",
"action": { "type": "command", "command": "npx tsc --noEmit -p . 1>&2" },
"timeout": 120
}
]
}
Duas decisões aqui. O --cache do ESLint evita reprocessar o repositório inteiro a cada salvamento. O redirecionamento 1>&2 resolve um descompasso de canal: o ESLint e o tsc escrevem o relatório no STDOUT, mas o Kiro só aproveita o STDOUT quando a saída é 0. Com o redirecionamento, o sucesso fica silencioso e, na falha, o relatório segue pelo STDERR, que é o canal que a documentação associa ao erro do hook.
2. Bloqueio de comando destrutivo de infraestrutura
{
"version": "v1",
"hooks": [
{
"name": "guard-infra-destrutiva",
"description": "Bloqueia destroy e delete de infraestrutura executados pelo agente",
"trigger": "PreToolUse",
"matcher": "shell",
"action": { "type": "command", "command": "bash tools/kiro-hooks/guard-infra.sh" },
"timeout": 10
}
]
}
#!/usr/bin/env bash
# PreToolUse em shell. Contrato: exit 0 libera, exit 2 bloqueia
# e o STDERR volta para o agente como explicação.
set -u
payload="$(cat)"
# Fail-closed: sem jq não há como ler o payload com segurança.
if ! command -v jq >/dev/null 2>&1; then
echo "guard-infra: jq ausente nesta máquina; bloqueando por segurança." >&2
exit 2
fi
# Lê o comando; se o campo mudar de nome, analisa a entrada inteira.
cmd="$(printf '%s' "$payload" | jq -r '.tool_input.command // (.tool_input | tostring)')"
padroes=(
'terraform[[:space:]]+destroy'
'terraform[[:space:]]+apply.*-auto-approve'
'cdk[[:space:]]+destroy'
'sam[[:space:]]+delete'
'aws[[:space:]]+[a-z0-9-]+[[:space:]]+(delete|terminate|remove)-'
'aws[[:space:]]+s3[[:space:]]+(rm|rb)[[:space:]].*--recursive|aws[[:space:]]+s3[[:space:]]+rb'
'kubectl[[:space:]]+delete'
)
for p in "${padroes[@]}"; do
if printf '%s' "$cmd" | grep -Eq -- "$p"; then
echo "Bloqueado pelo hook guard-infra (padrão: $p)." >&2
echo "Mudança destrutiva de infraestrutura passa pelo pipeline com aprovação humana." >&2
echo "Mostre o plano (terraform plan, cdk diff) e aguarde a decisão." >&2
exit 2
fi
done
exit 0
O código 2 como sinal de bloqueio está na referência de códigos de saída e no guia de hooks da IDE 1.0. Confirme o nome do campo tool_input.command com o hook de captura da seção anterior; o fallback para a entrada inteira mantém o guard útil se o nome do campo variar entre versões. Duas escolhas de método valem nota. O script é fail-closed: sem jq, bloqueia, porque um guard que libera quando quebra só existe no papel. E a mensagem no STDERR diz ao agente qual é o caminho aceito, o que reduz a chance de ele tentar variações do mesmo comando.
O limite também precisa ficar claro. O guard analisa texto, não semântica: um comando montado com variáveis, aliases, funções de shell ou um script intermediário passa pelos padrões sem ser reconhecido. Por isso ele é uma verificação adicional, e não um sandbox. O controle real sobre o que pode ser destruído é a credencial que a estação de trabalho usa, com permissão de leitura nas contas que importam.
Teste o guard como qualquer código, alimentando o STDIN à mão:
echo '{"tool_input":{"command":"terraform destroy -target=module.vpc"}}' \
| bash tools/kiro-hooks/guard-infra.sh; echo "rc=$?" # esperado: rc=2
echo '{"tool_input":{"command":"terraform plan"}}' \
| bash tools/kiro-hooks/guard-infra.sh; echo "rc=$?" # esperado: rc=0
3. Só leitura nas ferramentas MCP de produção
O padrão aqui começa no mcp.json: um servidor por conta, com nome que identifica o ambiente. O exemplo usa o AWS API MCP Server, que expõe as ferramentas call_aws e suggest_aws_commands e aceita a variável READ_OPERATIONS_ONLY para restringir a execução a operações de leitura. A configuração do workspace fica em .kiro/settings/mcp.json, conforme a documentação de MCP do Kiro.
{
"mcpServers": {
"awsprod": {
"command": "uvx",
"args": ["awslabs.aws-api-mcp-server@latest"],
"env": {
"AWS_REGION": "sa-east-1",
"AWS_API_MCP_PROFILE_NAME": "prod-somente-leitura",
"READ_OPERATIONS_ONLY": "true"
}
}
}
}
{
"version": "v1",
"hooks": [
{
"name": "mcp-prod-somente-leitura",
"description": "Na conta de produção, libera só operações de leitura",
"trigger": "PreToolUse",
"matcher": "@awsprod",
"action": { "type": "command", "command": "bash tools/kiro-hooks/mcp-prod-somente-leitura.sh" },
"timeout": 10
}
]
}
#!/usr/bin/env bash
# PreToolUse em @awsprod. O payload traz o ID interno mcp_awsprod_<ferramenta>.
set -u
payload="$(cat)"
command -v jq >/dev/null 2>&1 || { echo "mcp-prod: jq ausente; bloqueando." >&2; exit 2; }
tool="$(printf '%s' "$payload" | jq -r '.tool_name // empty')"
entrada="$(printf '%s' "$payload" | jq -c '.tool_input // {}')"
case "$tool" in
*suggest_aws_commands) exit 0 ;;
*call_aws)
# Encadeamento ou mais de uma chamada "aws": bloqueia sem tentar interpretar.
n=$(( $(printf '%s' "$entrada" | grep -Eo '(^|[^[:alnum:]_-])aws[[:space:]]' | wc -l) ))
if [ "$n" -ne 1 ] || printf '%s' "$entrada" | grep -Eq '&&|\|\||;|`|\$\('; then
echo "mcp-prod: comando encadeado ou com mais de uma chamada. Envie uma operação por vez." >&2
exit 2
fi
# Extrai a operação do comando da AWS CLI ("aws ec2 describe-instances").
op="$(printf '%s' "$entrada" | grep -Eo 'aws [a-z0-9-]+ [a-z0-9-]+' | head -n 1 | awk '{print $3}')"
case "$op" in
describe-*|list-*|get-*) exit 0 ;;
esac
echo "mcp-prod: '${op:-desconhecida}' não é operação de leitura." >&2
echo "Em produção, mudança entra por IaC e pipeline." >&2
exit 2 ;;
*)
echo "mcp-prod: ferramenta '$tool' fora da lista liberada para produção." >&2
exit 2 ;;
esac
São três camadas sobre o mesmo risco, cada uma com um dono diferente. A role do perfil prod-somente-leitura é o controle principal, avaliado pela própria AWS (o desenho de IAM com permissão mínima vale aqui). O READ_OPERATIONS_ONLY é o freio do servidor. O hook é a verificação do repositório, que deixa a regra explícita e versionada. O script é conservador de propósito: operação que não começa com describe-, list- ou get- é bloqueada, mesmo quando é leitura, e o ajuste da lista é decisão de revisão. A primeira checagem existe porque a extração da operação olha só a primeira ocorrência: sem ela, um comando como aws ec2 describe-instances && aws ec2 terminate-instances seria liberado pela leitura do início. Mesmo assim, vale o limite do hook anterior: o script analisa texto e não substitui a role somente leitura. Mantenha o nome do servidor sem hífen nem caracteres especiais: o ID interno é sanitizado, a regra de sanitização não está detalhada na documentação e o script compara pelo sufixo.
4. Testes ao fim de cada tarefa da spec
{
"version": "v1",
"hooks": [
{
"name": "testes-apos-tarefa",
"description": "Roda a suíte rápida quando uma tarefa da spec termina",
"trigger": "PostTaskExec",
"action": { "type": "command", "command": "bash tools/kiro-hooks/testes-da-tarefa.sh" },
"timeout": 300
},
{
"name": "barrar-tarefa-com-testes-vermelhos",
"description": "Impede a próxima tarefa enquanto a anterior deixou testes falhando",
"trigger": "PreTaskExec",
"action": {
"type": "command",
"command": "test ! -f .kiro-local/testes-vermelhos || { echo 'Testes vermelhos da tarefa anterior. Corrija e rode: bash tools/kiro-hooks/testes-da-tarefa.sh' >&2; exit 2; }"
},
"timeout": 5
}
]
}
#!/usr/bin/env bash
# PostTaskExec. Marca o estado em .kiro-local/ (no .gitignore).
set -u
mkdir -p .kiro-local
log="$(mktemp)"
if npm test --silent >"$log" 2>&1; then
rm -f .kiro-local/testes-vermelhos "$log"
echo "Testes verdes após a tarefa."
exit 0
fi
touch .kiro-local/testes-vermelhos
echo "Testes falharam após a tarefa. Últimas 40 linhas:" >&2
tail -n 40 "$log" >&2
rm -f "$log"
exit 1
PostTaskExec não bloqueia, então o hook sozinho só informa. O par com PreTaskExec, que pode bloquear, transforma a informação em regra: tarefa nova só começa com a suíte verde. A mensagem de bloqueio já diz ao agente como sair do estado vermelho. O tail limita o que volta para a conversa; um relatório de teste inteiro no contexto custa atenção do agente e não ajuda a corrigir.
5. Checklist de revisão no fim do turno, com confirmação (CLI V3)
Este exemplo foi desenhado para o Kiro CLI V3. O bloco confirm está na documentação de hooks, e a saída {"decision": "block"}, que solicita mais um turno ao agente, é documentada na página de migração do CLI V3. O guia de hooks da IDE 1.0 marca o Stop como não bloqueante e não descreve o confirm. Na IDE, o STDOUT do hook chega ao agente apenas como contexto.
{
"version": "v1",
"hooks": [
{
"name": "checklist-de-revisao",
"description": "Ao fim do turno, oferece o checklist objetivo sobre o diff",
"trigger": "Stop",
"action": { "type": "command", "command": "bash tools/kiro-hooks/checklist-revisao.sh" },
"timeout": 30,
"confirm": {
"question": "Rodar o checklist de revisão nas mudanças deste turno?",
"confirmCommand": "git diff --quiet HEAD && echo '{\"skip\": true}' || true",
"options": [
{ "id": "rodar", "label": "Rodar checklist", "run": true },
{ "id": "pular", "label": "Agora não", "run": false }
]
}
}
]
}
#!/usr/bin/env bash
# Stop. Checagens objetivas sobre o diff; adapte os padrões à sua stack.
set -u
achados=()
diff="$(git diff HEAD)"
arquivos="$(git diff --name-only HEAD)"
printf '%s' "$diff" | grep -Eq '^\+.*(console\.log|debugger)' \
&& achados+=("log de depuração no diff")
printf '%s' "$diff" | grep -Eq '^\+.*(AKIA[0-9A-Z]{16}|BEGIN [A-Z ]*PRIVATE KEY)' \
&& achados+=("possível segredo no diff")
if printf '%s' "$arquivos" | grep -Eq '^src/' \
&& ! printf '%s' "$arquivos" | grep -Eq '(^tests?/|\.test\.|\.spec\.)'; then
achados+=("código em src/ mudou e nenhum teste mudou")
fi
if [ ${#achados[@]} -gt 0 ]; then
motivo="Checklist de revisão: $(IFS=';'; echo "${achados[*]}"). Corrija ou justifique."
jq -n --arg r "$motivo" '{decision: "block", reason: $r}'
exit 0
fi
echo "Checklist de revisão: nada a apontar no diff deste turno."
O confirm só vale para hooks de comando no gatilho Stop. O confirmCommand roda antes da pergunta: quando imprime {"skip": true}, o hook é pulado naquele turno; quando falha, estoura o tempo ou imprime algo que não é JSON válido, valem a pergunta e as opções estáticas. O efeito prático é perguntar só quando existe diff. Quando o checklist encontra algo, a saída {"decision": "block", "reason": "..."} faz o CLI V3 dar mais um turno ao agente com o motivo; quando não encontra, o hook só informa.
Hook não substitui permissão
Hook é verificação. Política é o permissions.yaml. A documentação de permissões define regras por capacidade (fs_read, fs_write, shell, web_fetch, mcp, entre outras), com efeito deny, ask ou allow. A resolução é deny-overrides: deny > ask > allow, sem precedência entre escopos, então o efeito mais restritivo vence.
rules:
- capability: shell
match: ["terraform destroy*", "cdk destroy*", "sam delete*"]
effect: deny
- capability: shell
match: ["terraform apply*", "kubectl delete *", "git push*"]
effect: ask
- capability: fs_read
match: ["**/.env", "**/.env.*", "**/*.pem"]
effect: deny
Três detalhes de escopo definem como as camadas se distribuem no time:
- Hooks viajam com o repositório; permissões de workspace ficam com a pessoa. A permissão de escopo de workspace mora em
~/.kiro/workspace-roots/<hash>/permissions.yaml, fora do repositório, justamente para que um repositório clonado não injete regras. A política corporativa vai pelo escopo de administração (managed-settings.json). - Hook do repositório é código que roda na sua máquina. Os hooks são ativados no início da sessão. Trate
.kiro/hooks/e os scripts que eles chamam como código revisado: dono definido no CODEOWNERS e revisão obrigatória em pull request. A própria documentação de permissões coloca o diretório de hooks entre os caminhos em que escrita sempre pede confirmação. - Em execução headless, ask vira deny e o bloqueio é final. Sem cliente interativo, todo
aské tratado como negação. Segundo a documentação do modo headless, desde o CLI 2.27.1 as execuções não interativas do V3 carregam os hooks do agente e do workspace, um hook que bloqueia é final com--trust-all-toolsou--trust-tools, e solicitações de confirmação de hook são negadas quando a flag--trust-all-toolsnão está presente. A recomendação da página é escrever hooks que devolvam uma decisão final de liberar ou bloquear.
Defesa em camadas, com cada uma no papel em que é mais forte:
| Camada | Onde vive | O que garante |
|---|---|---|
| Permissões | permissions.yaml por usuário, workspace e administração | Política declarativa: o que o agente pode, deve perguntar ou nunca faz |
| Hooks | .kiro/hooks/ no repositório | Verificação com lógica: lê a entrada da ferramenta, decide e explica |
| Identidade na AWS | Roles do IAM e perfis por conta | Limite real de ação, avaliado pela AWS, independente da configuração da estação de trabalho |
| Pipeline | CI/CD com aprovação | Gate final antes de produção, independente da estação de trabalho |
A última linha é a que mais importa em infraestrutura: o caminho para produção é o pipeline de CI/CD com aprovação, e a estação de trabalho do desenvolvedor, com ou sem agente, fica com credenciais de leitura. É o mesmo princípio de segurança em cloud que aplico em qualquer conta AWS.
Atualização de 15/10/2026: o blog da AWS Brasil publicou em 15/09/2026 o Baseline de Segurança DevSecOps para o Kiro, um sample que empacota guards dePreToolUsepara shell e escrita, revisão noStope scan emPostTaskExec. O guard do artigo sai com 0 para permitir e com 2 para bloquear, o mesmo contrato dos exemplos acima, é testado com payloads que trazemtool_input.commandpara shell etool_input.pathpara escrita e tem o fail-closed como propriedade central. O artigo também registra dois limites que sustentam a defesa em camadas: oaskvem dopermissions.yaml, avaliado nativamente, e a denylist do guard não é um sandbox.
Armadilhas e como evitar
- Hook lento estourando o timeout. O padrão é 60 segundos. Suíte de testes completa em
PostFileSavetrava o fluxo a cada salvamento. Coloque o que é caro emPostTaskExec, use cache das ferramentas e definatimeoutexplícito por hook. - Loop de salvamento. Gatilhos de arquivo disparam só com mudanças do agente, então um formatador chamado por hook de comando não se redispara. O loop aparece com ação de agente em
PostFileSaveque pede ao agente para alterar o arquivo: ele salva, o hook dispara de novo. Para correção automática, prefira comando; para ação de agente em arquivo, restrinja o matcher e peça análise, não edição. - Ferramenta que só existe na sua máquina.
jq,uvxou um binário de scanner ausente na máquina de outra pessoa faz o hook falhar ou, pior, deixar de proteger em silêncio. Documente as dependências no README do diretório de hooks, prefira o que já está no gerenciador de pacotes do projeto (npx) e escreva guards fail-closed. - Código de saída herdado por acidente. Em
PreToolUse, saída 2 bloqueia. Ferramentas comuns usam 2 para outras coisas: ogrepsai com 2 em erro e o ESLint sai com 2 em erro de configuração. Um hook que repassa o código de saída de outra ferramenta pode bloquear sem querer. Normalize no script e saia com 0 ou 2 de forma explícita. - Saída ruidosa poluindo o contexto. Em saída 0, o STDOUT entra no contexto do agente. Relatório completo de lint ou de testes a cada evento disputa atenção com a tarefa. Silencie o sucesso e limite a falha às linhas que ajudam a corrigir.
- Hook global conflitando com o do projeto. Hooks de
~/.kiro/hooks/e do workspace rodam juntos. Um formatador global com regras diferentes das do repositório gera diff de estilo em toda tarefa. Reserve o escopo global para o que é pessoal e universal (um guard de segredos, por exemplo) e deixe convenção de projeto no projeto. - Matcher amplo demais. Um
PreToolUsesem matcher roda em toda chamada de ferramenta, inclusive leituras. As boas práticas oficiais pedem padrões específicos e escopo começando pequeno.
O fecho de método é tratar hook como código de produção do repositório: versionado, revisado em pull request, com testes de STDIN como os do guard acima rodando no pipeline, e com uma estrutura que qualquer pessoa entende em um minuto:
.kiro/
├── hooks/
│ ├── qualidade-ao-salvar.json
│ ├── guard-infra.json
│ ├── mcp-prod.json
│ ├── testes-por-tarefa.json
│ └── revisao-final.json
├── settings/
│ └── mcp.json
└── steering/
tools/kiro-hooks/
├── README.md # propósito, dependências e limites de cada hook
├── guard-infra.sh
├── mcp-prod-somente-leitura.sh
├── testes-da-tarefa.sh
├── checklist-revisao.sh
└── testes/ # casos de STDIN com código de saída esperado
.kiro-local/ # estado local, no .gitignore
Hooks bem desenhados tornam o comportamento em volta do agente previsível, auditável e igual para todo o time. É essa previsibilidade que sustenta a decisão de dar mais autonomia ao agente, uma etapa de cada vez.
Hooks versionados e a mesma checagem no CI, para o time inteiro
A RFX desenha com o seu time os hooks de verificação e de bloqueio, as regras de permissão e a etapa equivalente no pipeline, que cobre a edição manual. Para montar os primeiros hooks antes, a oficina gratuita Kiro na Prática tem turmas recorrentes.
Levar os hooks ao meu timeSe o objetivo é levar o Kiro para o time inteiro, com permissões, hooks e trilha de revisão desenhados para a sua conta AWS, conheça a adoção do Kiro com a RFX.
Perguntas frequentes
Onde ficam os hooks do Kiro?
Os hooks do projeto ficam em .kiro/hooks/, na raiz do repositório, um arquivo JSON por conjunto de hooks. No Kiro CLI V3 também existem hooks globais em ~/.kiro/hooks/, que valem para todos os workspaces e rodam junto com os do projeto. Os hooks são carregados automaticamente no início da sessão.
Qual a diferença entre a ação command e a ação agent?
A ação command roda um comando de shell na raiz do projeto e recebe o contexto da sessão em JSON pelo STDIN; é determinística, roda localmente e não consome créditos. A ação agent injeta um prompt na conversa e o agente age sobre ele; serve para orientação que depende de contexto e consome créditos porque inicia um novo ciclo do agente.
Como um hook bloqueia uma ferramenta no Kiro?
Um hook de ação command no gatilho PreToolUse bloqueia a chamada da ferramenta quando sai com código 2, e o STDERR volta para o agente como explicação, segundo a referência de códigos de saída. O guia de hooks da IDE 1.0 também lista PreTaskExec e UserPromptSubmit como bloqueantes; no CLI V3, UserPromptSubmit não bloqueia. Como a referência de códigos de saída cita só PreToolUse, vale validar os outros gatilhos no próprio ambiente. Saída 0 libera; qualquer outro código diferente de zero é tratado como erro do hook, não como bloqueio.
Hooks funcionam no modo headless do Kiro CLI?
Sim. Segundo a documentação do modo headless, desde o CLI 2.27.1 as execuções não interativas do V3 carregam os hooks do agente selecionado e do workspace. Um hook que bloqueia é final, com --trust-all-tools ou --trust-tools. Solicitações de confirmação de hook só são aprovadas automaticamente com --trust-all-tools; sem essa flag, são negadas.
Hook substitui o permissions.yaml do Kiro?
São camadas complementares. O permissions.yaml é a política: regras por capacidade com efeito deny, ask ou allow, em que deny prevalece sobre ask, que prevalece sobre allow. O hook é uma verificação adicional, com lógica própria, que roda antes ou depois de cada evento. O desenho recomendado combina as duas camadas com permissão mínima na conta AWS e um gate de aprovação no pipeline.