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, como PostFileSave ou PreToolUse.
  • hooks[].action: objeto com type igual a "command" (exige o campo command) ou "agent" (exige o campo prompt).

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 valor 0 desativa o limite. É ignorado em ações de agente.
  • enabled: false desliga o hook sem apagar o arquivo.
  • confirm: pede confirmação antes de um hook de comando no gatilho Stop, com pergunta e opções de botão.

O exemplo oficial roda o ESLint quando o agente salva um arquivo TypeScript:

.kiro/hooks/lint-on-save.json
{
  "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 SubmitUserPromptSubmitSimSimSó na IDE
Agent StopStopSimSimNão; no CLI V3 pode solicitar mais um turno
Session StartSessionStartSimSó V3Não
Session EndSessionEndNãoSó V3Não
Pre Tool UsePreToolUseSimSimSim
Post Tool UsePostToolUseSimSimNão
File CreatePostFileCreateSimSó V3Não
File SavePostFileSaveSimSó V3Não
File DeletePostFileDeleteSimSó V3Não
Pre Task ExecutionPreTaskExecSimSó V3Sim
Post Task ExecutionPostTaskExecSimSó V3Não
Manuallistado no CLI V3NãoListado, sem execução pelo /hooksNão se aplica
Hook manual legadoIDE 0.xSó os já existentesNãoNã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 PreTaskExec e UserPromptSubmit: 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 em PreTaskExec e informa que o UserPromptSubmit nã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.

.kiro/hooks/revisao-de-design.json
{
  "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, web e spec.
  • 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 read casa 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:

.kiro/hooks/capturar-payload.json (temporário)
{
  "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

.kiro/hooks/qualidade-ao-salvar.json
{
  "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

.kiro/hooks/guard-infra.json
{
  "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
    }
  ]
}
tools/kiro-hooks/guard-infra.sh
#!/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:

terminal
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.

.kiro/settings/mcp.json
{
  "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"
      }
    }
  }
}
.kiro/hooks/mcp-prod.json
{
  "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
    }
  ]
}
tools/kiro-hooks/mcp-prod-somente-leitura.sh
#!/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

.kiro/hooks/testes-por-tarefa.json
{
  "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
    }
  ]
}
tools/kiro-hooks/testes-da-tarefa.sh
#!/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.

.kiro/hooks/revisao-final.json
{
  "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 }
        ]
      }
    }
  ]
}
tools/kiro-hooks/checklist-revisao.sh
#!/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.

~/.kiro/settings/permissions.yaml
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-tools ou --trust-tools, e solicitações de confirmação de hook são negadas quando a flag --trust-all-tools nã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õespermissions.yaml por usuário, workspace e administraçãoPolítica declarativa: o que o agente pode, deve perguntar ou nunca faz
Hooks.kiro/hooks/ no repositórioVerificação com lógica: lê a entrada da ferramenta, decide e explica
Identidade na AWSRoles do IAM e perfis por contaLimite real de ação, avaliado pela AWS, independente da configuração da estação de trabalho
PipelineCI/CD com aprovaçãoGate 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 de PreToolUse para shell e escrita, revisão no Stop e scan em PostTaskExec. O guard do artigo sai com 0 para permitir e com 2 para bloquear, o mesmo contrato dos exemplos acima, é testado com payloads que trazem tool_input.command para shell e tool_input.path para escrita e tem o fail-closed como propriedade central. O artigo também registra dois limites que sustentam a defesa em camadas: o ask vem do permissions.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 PostFileSave trava o fluxo a cada salvamento. Coloque o que é caro em PostTaskExec, use cache das ferramentas e defina timeout explí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 PostFileSave que 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, uvx ou 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: o grep sai 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 PreToolUse sem 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:

estrutura-do-repositorio
.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.

Adoção do Kiro

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 time

Se 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.

Falar com um engenheiro