From d07c85c1bfc810360bec9061dcc0ce5ffeda7347 Mon Sep 17 00:00:00 2001 From: Luiz Antonio Busnello Date: Tue, 16 Jun 2026 18:19:44 -0300 Subject: [PATCH] chore: sync 1 lesson(s) from upstream MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auto-sync from openclaw-vps/infra/lessons/ (privado). Sanitização aplicada: tokens, números de telefone, IDs privados, emails, IPs Tailscale/públicos. Lessons sincronizadas: - 2026-06-16-openclaw-6.8-apply-patches-pattern-drift.md Revisar antes de merge — verificar se sanitização preservou significado técnico e não vazou nada. --- CHANGELOG.md | 1 + README.md | 2 +- ...penclaw-6.8-apply-patches-pattern-drift.md | 70 +++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 lessons/2026-06-16-openclaw-6.8-apply-patches-pattern-drift.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 31a7304..29299b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Versionamento independente do OpenClaw — kit segue semver próprio. ## [Unreleased] ### Adicionado +- **Lesson** [`lessons/2026-06-16-openclaw-6.8-apply-patches-pattern-drift.md`](lessons/2026-06-16-openclaw-6.8-apply-patches-pattern-drift.md) — (sem TL;DR — ver lesson para detalhes) - **Lesson** [`lessons/2026-06-09-openclaw-6.5-upgrade-patches-not-native-and-backup-retention.md`](lessons/2026-06-09-openclaw-6.5-upgrade-patches-not-native-and-backup-retention.md) — Upgrade planejado 2026.6.5 (primeiro stable do train 6.5, depois de 5 betas). A lição central é o oposto da do upgrade anterior: no 6.1, três monkey-patches viraram correções nativas e foram aposentad - **Lesson** [`lessons/2026-06-02-eval-saturation-and-7-bugs-cascade.md`](lessons/2026-06-02-eval-saturation-and-7-bugs-cascade.md) — Sessão de 4h45 disparada por agents Discord/WhatsApp travados. Causa imediata: eval do memoria-nox consumindo 395% CPU + 62.7% steal time do hypervisor Hostinger (noisy neighbor). Toto autorizou parar - **Lesson** [`lessons/2026-06-04-openclaw-6.1-upgrade-patches-native.md`](lessons/2026-06-04-openclaw-6.1-upgrade-patches-native.md) — Upgrade planejado de 2026.5.27 → 2026.6.1 (a 5.28 que estava agendada já havia sido superada; latest no npm = 6.1). Salto de changelog sem nenhuma seção "Breaking". O achado central: os três monkey-pa diff --git a/README.md b/README.md index 6b42f53..8d69954 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,7 @@ openclaw-update-toolkit/ │ ├── upgrade-from-v24-to-v29.md │ ├── recovery-from-fratricide-loop.md │ └── recovery-from-cost-explosion.md -└── lessons/ # 28 lições por incident — fonte de truth +└── lessons/ # 29 lições por incident — fonte de truth ``` --- diff --git a/lessons/2026-06-16-openclaw-6.8-apply-patches-pattern-drift.md b/lessons/2026-06-16-openclaw-6.8-apply-patches-pattern-drift.md new file mode 100644 index 0000000..8b8e67f --- /dev/null +++ b/lessons/2026-06-16-openclaw-6.8-apply-patches-pattern-drift.md @@ -0,0 +1,70 @@ +# Lesson: dist-bundle patches sofrem pattern-drift quando o upstream refatora a linha-alvo (OpenClaw 6.8) + +> Data: 2026-06-16 | Contexto: upgrade OpenClaw 6.6 → 6.8 | Severidade: P3 (degradação silenciosa, fail-open) + +## O que aconteceu + +Carregamos dois patches de dist-bundle no OpenClaw via um script `apply-patches.sh` rodado como `ExecStartPre` do systemd (idempotente, reaplica a cada restart): + +1. **empty-response** — preserva texto streamed quando o evento `result` do Claude CLI chega com texto vazio (`claude-live-session-*.js`). +2. **tool-only** — não dispara `FailoverError`/`empty_response` em turns que só fizeram tool calls (`cli-runner-*.js`). + +Ambos os patches funcionam por **string-match exato** da linha original do bundle minificado, com fallback de rollback se o match falha. + +No upgrade para 6.8, o **empty-response continuou aplicando**, mas o **tool-only falhou silenciosamente**. O log do `apply-patches`: + +``` +TARGET claude-tool-only: cli=cli-runner-.js +Patch 2 target not found in cli-runner file +FAIL claude-tool-only: marker missing after patch, restoring backups +END openclaw apply-patches: FAIL failures=1 +``` + +Como o `ExecStartPre` é prefixado com `-` (ignora falha), o gateway subiu normalmente — a falha **não apareceu** em `openclaw --version`, nem no boot log do gateway, nem no health. Só foi pega porque validamos os markers no dist manualmente pós-upgrade. + +## Causa-raiz + +O 6.8 **reescreveu a linha do `FailoverError`** no `cli-runner`. Forma antiga (que o patch procurava): + +```js +if (!assistantText && params.allowEmptyAssistantReplyAsSilent !== true) throw new FailoverError("CLI backend returned an empty response.", { +``` + +Forma nova no 6.8 (duas mudanças): + +```js +if (!assistantText && !output.didSendViaMessagingTool && params.allowEmptyAssistantReplyAsSilent !== true) throw attachCliMessagingDeliveryEvidence(new FailoverError("CLI backend returned an empty response.", { +``` + +- Adicionou `&& !output.didSendViaMessagingTool` na condição. +- Envolveu o `FailoverError` em `attachCliMessagingDeliveryEvidence(...)`. + +Nenhum dos patterns legacy do patch casou → `replacements.find()` retornou `undefined` → "target not found". + +## Fix aplicado + +Adicionada uma 3ª entrada (em 1ª posição) no array `replacements`, com o pattern do 6.8, inserindo `&& !output.hadToolCalls` na condição nova: + +```js +[ + '\t\tif (!assistantText && !output.didSendViaMessagingTool && params.allowEmptyAssistantReplyAsSilent !== true) throw attachCliMessagingDeliveryEvidence(new FailoverError("CLI backend returned an empty response.", {', + `\t\tif (!assistantText && !output.hadToolCalls && !output.didSendViaMessagingTool && params.allowEmptyAssistantReplyAsSilent !== true) throw attachCliMessagingDeliveryEvidence(new FailoverError("CLI backend returned an empty response.", { // ${marker}` +], +``` + +Patterns legacy mantidos como fallback (idempotência cross-version / rollback). Re-rodado → 3/3 OK, restart → ambos markers ativos. + +## Lições transferíveis + +1. **Pós-upgrade, validar o resultado do patch — não só a versão do binário.** `openclaw --version` verde NÃO significa que os patches pegaram. Conferir SEMPRE `tail /var/log/openclaw-apply-patches.log` por `FAIL`/`failures=`/`status=1` do `ExecStartPre`. Um `ExecStartPre` prefixado com `-` mascara a falha do boot. + +2. **String-match exato em bundle minificado é frágil a refactors.** Quanto mais "contexto" a string-âncora carrega (condições extras, wrappers), maior a chance de drift. Manter a âncora mínima e necessária; preferir múltiplos patterns alternativos com `find()`. + +3. **Patches downstream são dívida com juros por upgrade.** Cada versão upstream que toca a linha-alvo cobra ~15-30min de re-diagnóstico + adaptação. A saída definitiva é o merge upstream (aqui: PR que resolve empty-response/tool-only na raiz) — enquanto não merga, considerar tornar o matcher robusto (regex tolerante a variações da condição) em vez de string literal. + +4. **Falha fail-open ≠ falha invisível.** O design fail-open (gateway sobe mesmo se o patch falha) é correto pra disponibilidade, mas precisa de um alerta ativo (ex.: o `apply-patches.sh` poderia emitir um `notify-discord.sh warn` quando `failures>0`) — senão a degradação fica silenciosa até alguém checar manualmente. + +## Ação de follow-up sugerida + +- Adicionar alerta Discord no `apply-patches.sh` quando `failures>0` (hoje só loga em arquivo). +- Avaliar tornar o matcher do tool-only um regex tolerante (capturar a condição `if (!assistantText ... throw ... FailoverError("CLI backend returned an empty response.")` independente de cláusulas intermediárias).