Claude Fable é incansavelmente proativo

Claude Fable é incansavelmente proativo


Claude Fable é incansavelmente proativo

11 de junho de 2026

Após dois dias de experiência com Claude Fable 5, acho que a melhor maneira de descrevê-lo é incansavelmente proativo. Ele conhece muitos truques e utilizará praticamente qualquer um deles para atingir seu objetivo.

Vou ilustrar isso com um exemplo. Eu estava hackeando o Datasette Agent hoje quando notei uma falha: uma barra de rolagem horizontal que não deveria estar lá no prompt de bate-papo do menu de salto. Tirei esta captura de tela:

Claude Fable é incansavelmente proativo

Então comecei um novo claude sessão no meu datasette-agent checkout, arrastei a captura de tela e disse:

Look at dependencies to help figure out why there is a horizontal scrollbar here

Tive um palpite de que a causa estava na dependência do Datasette Agent (provavelmente o próprio Datasette) e sabia que o Fable era bom em pesquisar códigos de dependência, seja inspecionando arquivos instalados em seu próprio ambiente virtual site-packages ou referenciando um checkout local no disco. Dizer para começar com dependências pareceu uma boa aposta.

Me distraí com uma tarefa doméstica e me afastei do computador.

Quando voltei, alguns minutos depois, vi minha máquina abrir uma janela do navegador no meu Firefox normal e depois navegue até a caixa de diálogo em questão. Eu não havia dito ao Claude Code para usar qualquer automação de navegador e tinha certeza de que não era possível acionar movimentos do mouse ou atalhos de teclado em uma janela, então como foi fazer isso?

Observei fascinado enquanto ele continuava com suas explorações e depois o vi abrir uma janela do Safari em vez do Firefox. Também peguei este instantâneo do terminal Claude:

Captura de tela de duas chamadas de ferramenta Bash em uma interface de terminal escura. Primeiro: Bash (open -a Safari /tmp/textarea-scrollbar-test.html && sleep 4 && uv run --with pyobjc-framework-Quartz python - <<'EOF' import Quartz wins = Quartz.CGWindowListCopyWindowInfo(Quartz.kCGWindowListOptionOnScreenOnly, Quartz.kCGNullWindowID) para w em vitórias: if (w.get('kCGWindowOwnerName') ou '') == 'Safari' e 'textarea' em (w.get('kCGWindowName') ou '').lower(): print(w.get('kCGWindowNumber')) EOF) com saída 153551. Segundo: Bash(screencapture -x -o -l 153551 /tmp/safari-cases.png && echo ok) com saída ok.

O que estava fazendo lá com uv run --with pyobjc-framework-Quartz?

Acontece que o Fable criou seu próprio padrão para fazer capturas de tela das janelas do navegador. Ele estava usando Python para iterar por todas as janelas disponíveis em minha máquina e, em seguida, filtrar as janelas do Safari com strings esperadas, como "textarea" no nome da janela. Ele usou isso para encontrar o número da janela – um número inteiro como 153551 – que poderia então usar com o screencapture Ferramenta CLI para obter um PNG.

OK, tudo bem, essa é uma maneira legal de fazer capturas de tela. Mas do que estava tirando screenshots?

Acontece que ele estava escrevendo suas próprias páginas HTML para tentar recriar o bug, depois abrindo o Safari e capturando capturas de tela.

Aqui está a página /tmp/textarea-scrollbar-test.html que ele criou e a captura de tela tirada com screencapture -x -o -l 153551 /tmp/safari-cases.png:

Captura de tela de uma janela do navegador Safari mostrando uma página de teste da barra de rolagem da área de texto em file:///private/tmp/textarea-scrollbar-test.html. O texto da página diz: espessura da barra de rolagem: 17px | UA: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, como Gecko) Versão/26.4 Safari/605.1.15 | devicePixelRatio: 2. Seguem quatro casos de teste numerados, cada um com uma área de texto contendo o espaço reservado "Faça uma pergunta sobre seus dados...": 1. Plugin CSS exato (redimensionar: vertical, overflow padrão), 2. Plugin CSS + overflow-x: oculto, 3. Plugin CSS + redimensionar: nenhum e 4. Área de texto padrão simples, que é uma caixa muito menor com o espaço reservado agrupado em duas linhas.
(Tenho muitas abas abertas!)

OK, posso ver como ele está abrindo páginas de teste e fazendo capturas de tela, mas como diabos isso acionou o diálogo modal que deveria estar em teste? Isso só está disponível por meio de um clique ou atalho de teclado, e não consegui ver um mecanismo para executá-los no Safari.

Acabei descobrindo o que isso tinha feito.

Claude estava rodando em uma pasta que continha o código-fonte do aplicativo. Ele sabe o suficiente sobre o Datasette para poder executar um servidor de desenvolvimento local. Acontece que ele estava editando os próprios modelos do Datasette para adicionar JavaScript que acionaria o atalho de teclado correto assim que a janela fosse aberta, adicionando código como este:

script>
window.addEventListener("load", function () {
  setTimeout(function () {
    document.dispatchEvent(new KeyboardEvent("keydown", {key: "https://simonwillison.net/", bubbles: true}));
  }, 1200);
});
script>

1,2 segundos após a abertura da janela, este código aciona uma simulação / key, que é o atalho de teclado para abrir a caixa de diálogo modal.

Restava um desafio. Para entender o que estava acontecendo, Claude precisava executar JavaScript na página para fazer medições por si só.

Ele escreveu seu próprio aplicativo web personalizado para capturar informações via CORS, depois executou-o como um servidor local e abriu uma página com JavaScript que faria POST diretamente nele!

Aqui está o aplicativo web Python que ele escreveu, usando o pacote http.server da biblioteca padrão:

from http.server import HTTPServer, BaseHTTPRequestHandler

class H(BaseHTTPRequestHandler):
    def do_POST(self):
        n = int(self.headers.get("Content-Length", 0))
        open("/tmp/diag.json", "w").write(self.rfile.read(n).decode())
        self.send_response(200)
        self.send_header("Access-Control-Allow-Origin", "*")
        self.end_headers()
    def do_OPTIONS(self):
        self.send_response(200)
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Headers", "*")
        self.end_headers()
    def log_message(self, *a):  # quiet
        pass

HTTPServer(("127.0.0.1", 9999), H).serve_forever()

Tudo o que isso faz é aceitar uma solicitação POST cheia de JSON e gravá-la no /tmp/diag.json arquivo. Ele envia Access-Control-Allow-Origin: * cabeçalhos (incluindo de OPTIONS solicitações) para que o código em execução em outro domínio ainda possa se comunicar com ele.

Então Claude injetou este código no modelo que estava carregando em um navegador:

const host = document.querySelector("navigation-search");
const ta   = host.shadowRoot.querySelector("textarea");
const cs   = getComputedStyle(ta);
fetch("http://127.0.0.1:9999/diag", {
  method: "POST",
  body: JSON.stringify({
    dpr: window.devicePixelRatio,
    scrollWidth: ta.scrollWidth, clientWidth: ta.clientWidth,
    whiteSpace: cs.whiteSpace, width: cs.width,
  }),
});

Isso fez medições do