Por que escrever um arquivo grande dentro de um sandbox vive falhando


Uma escrita grande passa sem problemas no seu laptop. A mesma escrita contra um sandbox Modal volta com ValueError: embedded null byte. Contra outro backend o erro é mais estranho: o write_file reporta sucesso e o arquivo no disco tem zero bytes. Você culpa a rede, as permissões, um cache velho — e o motivo real é irritantemente banal: o payload inteiro era passado ao shell como argumento de linha de comando, e argumentos de linha de comando têm limite de tamanho e são visíveis para todo processo da máquina. Uma correção mesclada em 2026-09-26 (PR #124206) acaba com isso.

A causa raiz: dados de stdin no argv

cat file | bash -c '...' é trabalho normal de shell, mas os backends de sandbox pegaram um atalho: o payload destinado ao stdin de um comando era concatenado no próprio argumento do bash -c (o código chama isso de caminho heredoc).

Isso funciona bem até esbarrar em dois limites rígidos:

Limite Número O que acontece
Bytes NUL Um argumento não pode conter \0 ValueError: embedded null byte, a escrita falha de imediato
Comprimento do argumento O MAX_ARG_STRLEN do Linux é cerca de 128 KiB; o Modal é mais apertado (limite de ~64 KiB para exec-arg) Escritas maiores falham de imediato no Linux ou no Modal

A reprodução no PR é direta: um payload de 160 KiB contendo um NUL levanta embedded null byte na main, e o elemento argv que ele montou tem 165.003 bytes de comprimento. Leia a segunda parte de novo — mesmo sem o NUL, esse tamanho já passou da linha.

Há um segundo efeito fácil de passar batido: o argv é público. Qualquer processo na mesma máquina pode ler a linha de comando de outro processo. Concatenar conteúdo de arquivos — que pode conter chaves, tokens ou dados de usuário — no argv os estaciona silenciosamente na tabela de processos.

A correção: cada backend declara como entrega o stdin

O patch introduz uma declaração explícita de modo (tools/environments/base.py):

# Como execute() entrega o stdin ao _run_bash: "pipe" (stdin do processo, padrão),
# "payload" (repassado como stdin_data; o backend o transporta via seu
# SDK -- Modal/Modal gerenciado faz stream, Daytona/Vercel prepara um arquivo) ou
# "heredoc" (embutido no comando; nenhum backend embutido, só plugins).
_stdin_mode: str = "pipe"  # "pipe" | "payload" | "heredoc"

Nos três backends embutidos:

Backend O que ele faz agora
Modal _stdin_mode = "payload"; o stdin é transmitido em stream pelo SDK, byte a byte exato (surrogateescape), em blocos (1 MiB cada, ficando abaixo do limite de 2 MB do servidor legado)
Daytona O payload é enviado para um arquivo privado 0600 e depois alimenta o comando como exec 0< f; rm -f -- f; cmd
Vercel Mesma forma: os bytes chegam a um caminho privado, as permissões são definidas, e o stdin é redirecionado a partir dele

A versão do Daytona é ainda um pouco mais limpa: os bytes vão direto pelo SDK, sem arquivo temporário no host, e um stdin vazio pula a etapa de preparação por completo.

Olhe com atenção o prefixo de redirecionamento — duas ações, numa ordem deliberada:

exec 0< <staged-path> || exit $?      # o shell assume o descritor
rm -f -- <staged-path> || exit $?     # depois o arquivo é removido (unlink)
<command>

exec antes de rm significa que o arquivo some do disco durante toda a duração do comando, enquanto os dados continuam legíveis pelo descritor já aberto. As duas etapas viraram helpers compartilhados em BaseEnvironment (_staged_stdin_path, _redirect_stdin_from_file); o antigo caminho _embed_stdin_heredoc sobrevive apenas para backends de plugin.

A lógica de cancelamento também foi reescrita

A limpeza original tinha dois defeitos reais: qualquer erro de SDK parava o sandbox inteiro, e todo cancelamento o parava duas vezes. O patch divide o estado em três (cancelled / staged / dispatched) e o mantém sob o lock de ambiente:

  • um cancelamento que chega depois do upload mas antes do dispatch limpa o arquivo preparado (no Vercel sobrescreve, no Daytona apaga) e então para uma vez só;
  • um erro comum de SDK não para mais o sandbox;
  • um comando cancelado nunca é despachado.

O que isso significa para você

  • Nenhuma mudança de configuração. Cada backend trocou seu transporte; os chamadores não veem nada.
  • Casos que antes falhavam em silêncio ou aos berros — conteúdo com bytes NUL, escritas acima de mais ou menos 128 KiB — agora chegam byte a byte exatos.
  • Se você mantém um plugin de backend de sandbox, _embed_stdin_heredoc ainda está lá, mas backends novos devem declarar o modo payload pelos motivos daquela tabela.
  • Os três PRs foram mesclados em 2026-09-26, depois da tag da release v0.21.5, então eles estão na main. O hermes update os traz.

O padrão maior

Este trabalho tem um capítulo anterior: os PRs #122012 / #122011 corrigiram um heredoc amarrado ao comando errado, que é a outra face do problema da escrita byte a byte exata — um arquivo que parece escrito e contém os bytes errados. A correção de argv aqui cobre “nunca foi escrito de fato” mais “outro processo podia ler aquilo”. Juntas, elas apontam para um único ponto: quando você envia dados para um ambiente remoto, a linha de comando é o último lugar onde eles deveriam estar.

Se você depende de sandboxes remotos, siga lendo: como arquivos de mídia saem de um sandbox, o que um container Docker compartilhado significa para agentes em paralelo e o resto desta janela na página da release v0.21.5.