diff --git a/CHANGELOG.md b/CHANGELOG.md index 5692d86..02ed9aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ Versionamento independente do OpenClaw — kit segue semver próprio. ## [Unreleased] ### Adicionado +- **Lesson** [`lessons/2026-06-17-toolsallow-incompatible-claude-cli.md`](lessons/2026-06-17-toolsallow-incompatible-claude-cli.md) — (sem TL;DR — ver lesson para detalhes) +- **Lesson** [`lessons/2026-06-21-openclaw-6.8-to-6.9-upgrade-and-skill-symlink-escape.md`](lessons/2026-06-21-openclaw-6.8-to-6.9-upgrade-and-skill-symlink-escape.md) — (sem TL;DR — ver lesson para detalhes) +- **Lesson** [`lessons/2026-06-30-openclaw-6.11-upgrade.md`](lessons/2026-06-30-openclaw-6.11-upgrade.md) — (sem TL;DR — ver lesson para detalhes) +- **Lesson** [`lessons/2026-07-07-toolsallow-legacy-marker-breaks-cli-fallback.md`](lessons/2026-07-07-toolsallow-legacy-marker-breaks-cli-fallback.md) — - claude-cli cannot enforce runtime toolsAllow → fallback silencioso pra um modelo sem Bash → agente "some". +- **Lesson** [`lessons/2026-07-09-reply-session-init-conflict-and-contributing-upstream.md`](lessons/2026-07-09-reply-session-init-conflict-and-contributing-upstream.md) — - reply session initialization conflicted com messageIds distintos + sessão viva = transient drop benigno; com mesma messageId + sessão travada = wedge (real, precisa fix//reset). - **Lesson** [`lessons/2026-06-20-claude-cli-resume-hang-and-fallback-recovery.md`](lessons/2026-06-20-claude-cli-resume-hang-and-fallback-recovery.md) — Um turn provider=claude-cli model=claude-sonnet-4-6 trigger=user ficou 900s sem emitir um único byte (sem stream, sem rawLines) e foi morto pelo watchdog: claude live session turn failed ... durationM - **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 diff --git a/README.md b/README.md index 89c0752..b1a78b0 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/ # 30 lições por incident — fonte de truth +└── lessons/ # 35 lições por incident — fonte de truth ``` --- diff --git a/lessons/2026-06-17-toolsallow-incompatible-claude-cli.md b/lessons/2026-06-17-toolsallow-incompatible-claude-cli.md new file mode 100644 index 0000000..92a5444 --- /dev/null +++ b/lessons/2026-06-17-toolsallow-incompatible-claude-cli.md @@ -0,0 +1,95 @@ +# Lesson: `toolsAllow` incompatível com backend `claude-cli` + +**Data:** 2026-06-17 +**Severidade:** Alta (crons críticos falharam silenciosamente por múltiplos dias) +**Agente:** Forge + +--- + +## O que aconteceu + +Três crons críticos passaram a falhar com erro consistente: + +``` +FallbackSummaryError: All models failed (2): + anthropic/claude-haiku-4-5: CLI backend claude-cli cannot enforce runtime toolsAllow; + use an embedded runtime for restricted tool policy (unknown) + openai/gpt-5.5: auth refresh request timed out after 10s (timeout) +``` + +**Jobs afetados:** +| Job | Agent | `toolsAllow` | Erros consecutivos | +|-----|-------|-------------|-------------------| +| `relatorio-eod` | main | `["exec", "session_status"]` | 4 | +| `auto-update-skills-clawhub` | main | `["exec"]` | 4 | +| `workspace-git-autopush` | forge | `["exec"]` | 4 | + +**Job proativamente corrigido antes de falhar:** +| Job | Agent | `toolsAllow` | +|-----|-------|-------------| +| `cipher-weekly-full` | cipher | `["exec", "message"]` | + +--- + +## Root cause + +O campo `toolsAllow` no payload de cron jobs define uma allowlist de ferramentas — quando presente, o runtime tenta restringir o agente apenas a essas ferramentas. + +**O problema:** o backend `claude-cli` (usado pelo `anthropic/claude-haiku-4-5` e todos os modelos Anthropic via CLI) **não suporta enforcement de `toolsAllow` em runtime**. A verificação passou a ser feita de forma mais rígida em alguma versão recente do gateway, rejeitando o job antes mesmo de iniciar a execução. + +O fallback para `openai/gpt-5.5` (OAuth) também falhou por timeout de auth refresh — deixando os jobs sem fallback funcional. + +--- + +## Por que alguns jobs com `toolsAllow` continuaram funcionando? + +Os jobs de briefing (`prepare-briefing-context`, `daily-briefing-delivery`, etc.) também tinham `toolsAllow: ["exec"]` mas não falharam imediatamente. Hipótese: o enforcement mais rígido foi introduzido gradualmente ou os jobs afetados têm alguma combinação específica (ex: `lightContext: true` + `toolsAllow`). + +**Ação preventiva recomendada:** remover `toolsAllow` de todos os cron jobs que usam modelos Anthropic via CLI. + +--- + +## Fix aplicado + +Removido `toolsAllow` (setado para `null`) nos 4 jobs via `cron.update`: + +```bash +# Exemplo do patch aplicado em cada job +{ + "patch": { + "payload": { + "toolsAllow": null + } + } +} +``` + +**Por que é seguro remover:** os jobs são todos `sessionTarget: "isolated"` — sessões efêmeras sem histórico de ferramenta. O comportamento do agente já é constrangido pelo prompt (ex: "Execute X, responda HEARTBEAT_OK"). A restrição via `toolsAllow` era redundante. + +--- + +## Regra para novos crons + +> **Nunca use `toolsAllow` em cron jobs com `sessionTarget: "isolated"` e modelo Anthropic (claude-cli).** + +Se precisar restringir ferramentas em cron jobs: +1. Use **embedded runtime** (não claude-cli) — ex: `openai/gpt-4o` com API key direta +2. Ou constranja via **prompt** — mais confiável e portável entre runtimes + +--- + +## Lições aprendidas + +1. **`toolsAllow` ≠ segurança robusta** — depende de suporte do runtime e pode quebrar silenciosamente +2. **Fallback chain deve ser testada** — `gpt-5.5` OAuth estava com timeout; fallback ineficaz +3. **Crons críticos precisam de `failureAlert`** — `workspace-git-autopush` não tinha alerta configurado, ficou 4 falhas silencioso +4. **Alertas de falha chegam tarde** — `relatorio-eod` alertou só na 3ª falha; para crons diários isso é 3 dias perdidos + +--- + +## Ações de follow-up recomendadas + +- [ ] Auditar todos os crons restantes com `toolsAllow` e remover onde modelo = Anthropic/CLI +- [ ] Verificar health do OAuth `openai/gpt-5.5` — auth refresh timeout pode indicar token próximo de expirar +- [ ] Adicionar `failureAlert` ao `workspace-git-autopush` (atualmente sem alerta) +- [ ] Considerar reduzir `failureAlert.after` em crons críticos de 3 para 1 diff --git a/lessons/2026-06-21-openclaw-6.8-to-6.9-upgrade-and-skill-symlink-escape.md b/lessons/2026-06-21-openclaw-6.8-to-6.9-upgrade-and-skill-symlink-escape.md new file mode 100644 index 0000000..3f74903 --- /dev/null +++ b/lessons/2026-06-21-openclaw-6.8-to-6.9-upgrade-and-skill-symlink-escape.md @@ -0,0 +1,47 @@ +# 2026-06-21 — Upgrade OpenClaw 6.8→6.9 + 4 achados (Patch 2 re-drift, emoji path, native claude dedup, skill symlink-escape) + +> Sessão noturna (~20:30→22:05 BRT). Upgrade estável 6.8→6.9 + revisão completa pós-upgrade (5 itens) + fix sistêmico de skills quebradas em 4 agents. Zero downtime. Antes, fechamento do PR #90450 (rebase + diagnóstico de CI flaky do upstream). + +## Contexto +6.9 estável saiu 2026-06-21 ~01:44 UTC. Motivação: retry de empty/thinking-only post-tool turns (#92191/#92383), auth-profiles→SQLite (#93156), restart de gateway pós-update falho (#92111), nox-mem WAL/reindex (#92891), plugin recovery (#93325), secret redaction (#93333). Não-urgente (sem CVE/crash forçando). PR #90450 segue aberto → patches mandatórios. + +## Upgrade (6 fases, zero downtime) +`npm install -g openclaw@2026.6.9` (`c645ec4`), 11s. 5 plugins `@openclaw/{acpx,codex,discord,slack,whatsapp}`→6.9. NRestarts=0, smoke `infer`→`provider:anthropic UPGRADE_OK`, Slack socket connected + WhatsApp linked. + +## Achado 1 — Patch 2 (tool-only) RE-DRIFTOU: inline → BLOCO +O 6.8 já tinha quebrado o Patch 2 (linha do `FailoverError`). No 6.9 quebrou de novo, mas de forma DIFERENTE: a condição virou **bloco** onde era **inline**. +- 6.8: `if (!assistantText && !output.didSendViaMessagingTool && ...!== true) throw attachCliMessagingDeliveryEvidence(new FailoverError(...))` +- 6.9: `if (!assistantText && !output.didSendViaMessagingTool && ...!== true) {` `\n const emptyOutputDiagnostics = ...;` `\n throw attach...` + +**Fix:** nova 1ª variante no array `replacements` do `apply-patches.sh` que casa `...!== true) {` e insere `&& !output.hadToolCalls`. **Novidade boa:** o parser do 6.9 já emite `hadToolCalls = raw.includes('"tool_use"')` NATIVO — upstream foi meio caminho do PR #90450, só não conectou no cli-runner (Patch 1/live agora só marca, sem mudar lógica). Hashes 6.9: `claude-live-session-BFQhL7uS.js` (empty+debug) + `cli-runner-BOYyOIRI.js` (tool-only). +**Lição:** a linha-alvo do tool-only é frágil a refactor — esperar drift NOVO a cada upgrade. Validar SEMPRE `tail /var/log/openclaw-apply-patches.log` por `FAIL`. + +## Achado 2 — Emoji patch (regra 2.1) mudou de path +6.9 moveu `status-message-*.js` de `~/.openclaw/plugin-runtime-deps/openclaw-*/dist/` (que sumiu) para o **core dist** `/usr/lib/node_modules/openclaw/dist/`. O `reapply-session-status-emoji-patch.py` dava `no targets found`. Pattern do template-literal NÃO mudou. **Fix:** glob do script atualizado pra cobrir os 2 paths (espelho + VPS). ⚠️ Agora vive no core dist → some a cada `npm install -g`, reaplicar pós-upgrade. + +## Achado 3 — DOIS claudes (dedup do harness) +`claude --version` no SSH mostrava 2.1.87 (enganoso). Havia 2 instalações: +- `/usr/bin/claude` → `@anthropic-ai/claude-code` npm global = **2.1.169** (o harness REAL; PATH do systemd `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/snap/bin` NÃO inclui `/root/.local/bin`, logo o gateway SEMPRE usa este) +- `/root/.local/bin/claude` → `~/.local/share/claude/versions/2.1.87` (native installer, só no login shell interativo) +**Fix:** removido o native (reversível, backup `/root/.trash-claude-native-*` 218M; 0 crons/scripts dependiam — só shell-snapshots do Claude Code o referenciavam). Depois **harness atualizado 2.1.169→2.1.185** (npm -g; credentials `chattr +i` intactas; validado por turns REAIS de prod sonnet+haiku `provider=claude-cli` $0). + +## Achado 4 — Skill symlink-escape QUEBROU skills de 4 agents (regressão 6.9, #86719) +O 6.9 endureceu skill symlink handling (release "stale plugin skill symlinks are repaired" #86719) e passou a **rejeitar silenciosamente** skills expostas via symlink que resolvem fora do root (`[skills] Skipping escaped skill path outside its configured root ... reason=symlink-escape`). Skills instaladas via ClawHub vivem em `/.agents/skills/` e eram expostas via symlink em `/skills/` → todas puladas. +**Impacto:** forge 15, boris 12, atlas 12 (passivo), cipher 1 — skills exclusivas (payment-assistant, binance, fiat, p2p, trading-signal…) silenciosamente perdidas. +**Fix:** resolver symlinks → conteúdo real dentro do root (discriminado: `.agents/skills`→cp; `workspace/skills`→rm redundante; broken→rm). 0 escapes pós-restart. Contagem final: forge 36, boris 20, atlas 28, cipher 10, nox 15, lex 8. Backups `/root/.openclaw/.skill-symlink-backup/-`. Memória `symlinks-agents-skills-redundantes` reescrita (estava perigosamente errada — mandava "safe deletar"). ⚠️ Re-checar no próximo upgrade (npm install -g mexe em skill symlinks). + +## Itens da revisão que NÃO geraram ação +- **#4 SQLite plugin metadata conflict:** won't-fix benigno. `doctor --fix --non-interactive` NÃO migrou ("left in place" por conflicting metadata) E reinstalou providers fantasma como efeito colateral → REVERTIDO. Conflito é inerente ao npm-direto (regra 2.2). Plugins funcionam. +- **#3 providers fantasma:** removidos 12 (`config unset plugins.entries.*` + `plugins.allow` sem firecrawl/perplexity/kimi) → validate 4→1 warning (só graph-memory, design). + +## ⚠️ Gotcha crítico: `doctor --fix` é perigoso em prod +Mesmo `--non-interactive`: NÃO migrou o que prometeu E reinstalou providers que eu tinha limpado. Haiku NÃO está em `agents.defaults.models` (risco de migração haiku→sonnet). **Não rodar `doctor --fix` casualmente** — snapshot de invariantes + backup granular + reverter se driftar. + +## Estado final +OpenClaw 6.9 (`c645ec4`) · harness 2.1.185 · 4 patches (empty-response/tool-only/debug-capture/emoji) · 5 plugins · config 1 warning · haiku preservado (4+9) · skills de 6 agents carregando · 1 só claude · gateway active NRestarts=0. + +## Pendências +- Deletar backups: `/root/.trash-claude-native-*` (218M, após validar uns dias) + `.skill-symlink-backup/*` +- Sync lesson pro toolkit público (`sync-to-toolkit.sh`) +- Vigiar skill symlink-escape + emoji patch path no PRÓXIMO upgrade +- PR #90450: 3 shards CI vermelhos = flaky universal do upstream (provado em 7 PRs), aguardando maintainer diff --git a/lessons/2026-06-30-openclaw-6.11-upgrade.md b/lessons/2026-06-30-openclaw-6.11-upgrade.md new file mode 100644 index 0000000..82951ce --- /dev/null +++ b/lessons/2026-06-30-openclaw-6.11-upgrade.md @@ -0,0 +1,34 @@ +# 2026-06-30 — Upgrade OpenClaw 6.10 → 6.11 (limpo, Patch 2 estável) + +**Contexto:** Toto pediu análise dos releases. Recon revelou que a doc dizia "6.9" mas a VPS já estava em **6.10** (subiu 28/jun). Salto real = 1 minor (6.10→6.11). Motivado por fixes de fallback chain. + +## Upgrade (6 fases, zero downtime, ~5min) +0. Backup `/var/backups/preupgrade-6.11-20260630-1632` (openclaw.json 43k + chain + catálogo + plugins-list + health + dist-hashes). NRestarts baseline=3. +1. `npm install -g openclaw@2026.6.11` (19s) → `e085fa1`. +2. `apply-patches.sh` → **3/3 `patch applied`, Patch 2 tool-only NÃO driftou.** Hashes 6.11: `claude-live-session-ChcH88T3.js` (empty+debug) + `cli-runner-H97DdWc0.js` (tool-only). +3. Phase 7: 5 plugins `@openclaw/{acpx,codex,discord,slack,whatsapp}` → 2026.6.11. +4. `systemctl restart openclaw-gateway` → apply-patches boot 3/3 `already patched`, `END OK`, NRestarts=0. +5. Validação: smoke `infer model run`→`PONG_611` (provider:anthropic claude-sonnet-4-6), chain íntegra (sonnet-4-6→gpt-5.5→kimi-k2.6→glm-5.2), 3 canais up (Discord@Maestro/Slack healthy/WhatsApp linked), 8 agents, 0 ERROR/fratricide. + +## Achado 1 — Patch 2 (tool-only) ESTÁVEL no 6.10 E 6.11 +Depois de driftar no 6.8 (inline→`didSendViaMessagingTool`) e 6.9 (inline→**bloco**), o pattern do 6.9 (casa `...!== true) {` e insere `&& !output.hadToolCalls`) **sobreviveu intacto no 6.10 e no 6.11**. Sinal de que o `cli-runner` estabilizou nessa forma. **Não baixar a guarda:** continuar validando `tail /var/log/openclaw-apply-patches.log` por FAIL a cada upgrade — o drift é imprevisível. + +## Achado 2 — Warnings de provider plugin são PRÉ-EXISTENTES (não regressão) +`openclaw agents`/doctor read-only listou `plugins.entries.{moonshot,fireworks,tencent,venice,vercel-ai-gateway}: plugin not installed`. **Classificação correta:** `jq '.plugins.entries | keys' openclaw.json.bak` confirmou que `moonshot/fireworks/tencent` já estavam no config ANTES do upgrade. São entries de config de provider sem o plugin-provider instalado — mas a chain usa `agentRuntime` (kimi via ACP, glm via endpoint anthropic-compat), não esses plugins. Benigno. **Lição de método:** sempre comparar warning suspeito contra o snapshot pré-upgrade antes de "consertar" — quase reinstalei plugins desnecessários. + +## Achado 3 — "conflicting plugin install metadata" = efeito esperado do Phase 7 +Após `npm install` dos 5 plugins externos, o doctor reporta metadata conflitante no SQLite plugin-index ("Left plugin install index in place"). É o efeito conhecido de instalar via npm direto (workaround obrigatório do bug `plugins install` destrutivo). Install-index fica em JSON, plugins carregam normal. Não acionar `doctor --fix` por causa disso. + +## Por que 6.11 (fixes que importam pra nós) +- **#95508 / #95489** — `claude-cli` out-of-credits **bypassava a fallback chain** (entregava texto de erro como final). Ataca direto a fragilidade `All models failed`. +- **#95420 / #95400** — credit failures e usage-limit do Codex agora seguem a chain (gpt-5.5 expira 09/jul). +- **#95652** — activate selected harness plugins (anti-unregistered). +- **#95624** — long-context tool-result prompts cache-stable (1M). +- **#94148** — `doctor --fix` não-interativo não reinicia gateway sozinho (alinha com nossa regra "doctor --fix perigoso em prod"). + +## Lições transferíveis +1. **Doc drift entre upgrades:** o forge subiu pra 6.10 em 28/jun mas não atualizou CLAUDE.md/MEMORY.md (ficou "6.9"). Sempre confirmar versão real via SSH antes de planejar — `openclaw --version` é a fonte de verdade. +2. **Patch 2 pode estar estabilizando** mas validar o log por FAIL segue mandatório. +3. **Warning ≠ regressão:** classificar contra snapshot pré-upgrade. + +Memória: `[[openclaw-61-upgrade-patches-native]]`. Plan: `plans/2026-06-30-openclaw-v2026.6.11-upgrade.md`. diff --git a/lessons/2026-07-07-toolsallow-legacy-marker-breaks-cli-fallback.md b/lessons/2026-07-07-toolsallow-legacy-marker-breaks-cli-fallback.md new file mode 100644 index 0000000..b976e7c --- /dev/null +++ b/lessons/2026-07-07-toolsallow-legacy-marker-breaks-cli-fallback.md @@ -0,0 +1,69 @@ +# Lesson: cron agent "some sem erro" — tool-cap legado sem marker força fallback `claude-cli → gpt-5.5` + +**Data:** 2026-07-07 +**Severidade:** Alta (digest crítico entregou vazio por dias, sem nenhum erro visível) +**Versão:** OpenClaw 2026.6.11 + +--- + +## Sintoma + +Um cron `agentTurn` (ex.: um digest que depende de `exec`/Bash) **para de entregar, sem lançar erro visível pro usuário**. O agente simplesmente "some": a sessão roda, mas a saída chega vazia, às vezes com um `announce give up (retry-limit)`. + +Nos logs do gateway, o rastro é este: + +``` +[model-fallback/decision] model fallback decision: decision=candidate_failed + requested=anthropic/claude-... candidate=anthropic/claude-... + reason=unknown next=openai/gpt-5.5 + detail=CLI backend claude-cli cannot enforce runtime toolsAllow; use an embedded runtime for restricted tool policy +``` + +O backend `claude-cli` **não consegue impor um `toolsAllow` em runtime** — por design ele lança esse erro em vez de silenciosamente ignorar o cap (ignorar seria um downgrade de segurança). O gateway então cai no fallback (`gpt-5.5`), que **não tem `exec`/Bash** — logo qualquer agente que dependa de shell produz saída vazia. O usuário nunca vê um erro: o digest só "some". + +## Causa-raiz + +A correção nativa **#91499** ("preserve scheduled turn tool policy", merge 2026-06-15, presente no build 6.11) resolve isso **apenas para caps auto-stampados** — os que carregam o marker `"toolsAllowIsDefault": true` no `job_json`. Para esses, `resolveCliRuntimeToolsAllow(toolsAllow, toolsAllowIsDefault)` retorna `undefined` (dropa o cap) e o `claude-cli` não lança. + +**O buraco:** jobs criados ANTES de 15/jun carregam um tool-cap **sem** esse marker, e **não há backfill** que os migre. Eles continuam quebrando indefinidamente mesmo depois do upgrade que trouxe o #91499. O cap costuma ser um snapshot auto-stampado de ~60 tools de superfície (nem contém `exec`), então o agente perde justamente a capacidade que precisava. + +## Diagnóstico (como confirmar) + +1. **Log:** procurar `cannot enforce runtime toolsAllow` no journal do gateway. +2. **Store:** no SQLite do gateway (`state/openclaw.sqlite`, tabela `cron_jobs`), listar jobs com cap não-vazio, sem marker, em model `anthropic/*`: + +```sql +SELECT name FROM cron_jobs +WHERE payload_tools_allow_json IS NOT NULL + AND payload_tools_allow_json NOT IN ('', 'null', '[]') + AND job_json NOT LIKE '%toolsAllowIsDefault":true%' + AND payload_model LIKE 'anthropic/%'; +``` + +Se um cron `anthropic/*` "some sem erro", esses dois sinais confirmam a causa. + +## Fix + +```bash +openclaw cron edit --clear-tools +``` + +`--clear-tools` é first-class e remove o cap do job (o turn passa a usar todas as tools). Jobs recriados no 6.11 já nascem imunes (o criador de superfície irrestrito não recebe cap). + +**NÃO** usar: +- `sandbox.sessionToolsVisibility` — **alavanca errada**: governa o targeting cross-session dos tools `sessions_*`, não o cap do cron. +- escrita SQL direta no store — frágil e fora do contrato do gateway; use o CLI. + +## Guard durável + +Como o sintoma é invisível, vale um canário horário que detecta a recorrência: cron com cap-não-vazio + sem-marker + `anthropic/*` no store, OU o log `cannot enforce` na última hora → alerta. (Na nossa VPS: `cron-toolsallow-guard.sh`, `37 * * * *`.) + +## Por que NÃO virou PR + +O fix já é nativo (#91499); o resíduo é **migração de jobs legados**, resolvida com `--clear-tools` (ou recriando o cron). Abrir PR duplicaria trabalho já mergeado. A lição anterior (`2026-06-17-toolsallow-incompatible-claude-cli`) cobre o caso geral "toolsAllow incompatível com claude-cli"; **esta é o twist pós-#91499**: o fix existe mas não faz backfill, então legados seguem quebrando até serem tocados. + +## TL;DR + +- `claude-cli cannot enforce runtime toolsAllow` → fallback silencioso pra um modelo sem Bash → agente "some". +- #91499 conserta caps COM marker `toolsAllowIsDefault`; legados pré-15/jun sem marker não têm backfill. +- Fix: `openclaw cron edit --clear-tools`. Guard horário pra recorrência. diff --git a/lessons/2026-07-09-reply-session-init-conflict-and-contributing-upstream.md b/lessons/2026-07-09-reply-session-init-conflict-and-contributing-upstream.md new file mode 100644 index 0000000..379980d --- /dev/null +++ b/lessons/2026-07-09-reply-session-init-conflict-and-contributing-upstream.md @@ -0,0 +1,62 @@ +# Lesson: `reply session initialization conflicted` é (quase sempre) benigno — e como contribuir upstream sem errar + +**Data:** 2026-07-09 +**Severidade:** Baixa (o bug é benigno) — mas **alto valor de método** (evita PR duplicado + comentário errado em repo público) +**Versão:** OpenClaw 2026.6.11 + +--- + +## Parte 1 — O erro `reply session initialization conflicted` + +Se o log do gateway está cheio de: + +``` +[diagnostic] message dispatch completed: ... source=replyResolver outcome=error + error="Error: reply session initialization conflicted for agent:::" +``` + +...na maioria dos casos **é benigno**. A inicialização da reply-session usa um commit otimista (compare-and-swap na revisão do session-store). Quando dois eventos disputam a init da mesma `sessionKey` — tipicamente um trigger (mensagem do usuário, heartbeat, cron) chegando **enquanto um turn anterior ainda roda** —, o perdedor do CAS lança esse erro e o inbound é descartado. + +### Dois modos distintos sob a MESMA mensagem de erro + +A confusão perigosa: **dois bugs diferentes produzem exatamente esse texto.** Distinga antes de agir: + +| | **Wedge permanente** | **Transient drop** (o comum) | +|---|---|---| +| Padrão | a **mesma** messageId re-bate o conflito pra sempre | messageIds **distintos**, cada um perde 1× e é descartado | +| Sessão | **travada** — todo turn seguinte falha, só `/reset` cura | **saudável** — crons/digests seguem rodando na mesma key | +| Sobrevive a restart? | sim (é keyed na sessionKey persistida) | não se aplica (não trava) | +| Impacto | real (sessão inutilizável, tool-results voltam vazios) | benigno (perde só heartbeat/cron, não mensagens do usuário) | + +**Como diagnosticar rápido:** os messageIds nas linhas de erro são iguais (wedge) ou distintos e crescentes (transient)? Os digests/crons daquele agente continuam entregando (transient) ou pararam de vez (wedge)? + +### O que NÃO investigar (becos sem saída que parecem promissores) + +- **"Um webhook está despejando eventos"** — cheque os hits reais do webhook; no nosso caso `/hooks/github` teve **0 hits/24h**. +- **"Algo está postando no canal"** — o canal Discord estava **vazio**; os IDs eram de turns internos (cron/heartbeat), não mensagens visíveis. +- **"Os crons do agente estão colidindo"** — se os crons têm horários espaçados, não há contenção de cron pra desfazer; a sobreposição vem de trigger interativo + heartbeat caindo dentro da janela de um turn longo. **Nem sempre há uma alavanca de config pra puxar.** + +### Mitigação local + +O fix determinístico é upstream (dar aos paths de inbound o mesmo retry/spool que alguns canais já têm, ou serializar a init por sessionKey). Enquanto isso, se um canário de "heartbeat do canal" ficar em false-FAIL por causa desse ruído benigno, **silencie o canário** (reversível) em vez de mascarar o log — o bug em si não exige ação. + +## Parte 2 — Como contribuir upstream sem errar + +Esse erro tem **dezenas de issues relacionadas** no repo. Antes de "descobrir um bug novo e abrir um PR", siga este checklist — ele evitou, nesta sessão, um PR duplicado e um comentário público errado. + +### 1. Busque issues/PRs existentes ANTES de escrever qualquer coisa +A assinatura do erro já tinha ~65 issues, incluindo uma aberta **no mesmo dia, na mesma versão, por outro usuário**, com o label **`no-new-fix-pr`** (o próprio projeto pedindo pra não abrir PR de fix) e `needs-product-decision`. Abrir PR ali seria ruído. + +### 2. Não deixe um fix fechar o bug errado +Havia um PR de "self-heal" (para o **wedge permanente**) sendo tratado pelo bot de triagem como o fix primário do **transient drop** — dois bugs distintos sob a mesma mensagem. Se você tem evidência de que são separados, **diga isso**, senão o transient é fechado por engano quando o self-heal landar. + +### 3. Valide seu raciocínio com modelos adversariais — mas verifique todo claim de código no código +Rodamos o raciocínio por dois modelos independentes em modo "challenge". Foi ótimo pra tese — **mas um deles produziu um "achado de código" plausível e FALSO** (afirmou que um retry reusava um snapshot velho; ao ler o fonte, o snapshot era re-lido). Regra dura: **qualquer alegação com número de linha / mecanismo vai ao código real antes de ir a público.** Um `git clone` + `git show` de 30s te salva de ser corrigido pela equipe do projeto — e pegou também um número mal-atribuído que **eu mesmo** ia postar. + +### 4. Quando o fix já está em movimento, contribua EVIDÊNCIA, não um PR concorrente +O que faltava na issue era uma repro de produção ao vivo (o revisor automatizado admitiu não ter rodado uma). Postamos exatamente isso — a distinção transient-vs-wedge + logs redigidos num bloco `
` colapsável (corpo curto engaja, prova a um clique). Isso destrava a decisão de produto sem duplicar código. + +## TL;DR + +- `reply session initialization conflicted` com **messageIds distintos + sessão viva** = transient drop **benigno**; com **mesma messageId + sessão travada** = wedge (real, precisa fix/`/reset`). +- Antes de abrir PR: **busque issues existentes** (procure o label `no-new-fix-pr`), **não deixe um fix fechar o bug errado**, **verifique claims de código no fonte**, e **contribua evidência de campo** quando o fix já está em andamento.