# Learnings — Padrões bem-sucedidos _Registro de padrões que funcionam, para replicar._ --- ## [LRN-20260520-003] gitea-api-autenticacao-completa **Logged**: 2026-05-20T12:31:00-03:00 **Priority**: high **Status**: reference **Area**: devops | api ### Summary Gitea API suporta 5 métodos de autenticação — todos usam o mesmo API token ### Details Fontes estudadas: https://docs.gitea.com/development/api-usage **Métodos de autenticação (ordem de preferência):** 1. `Authorization: token ` — mais comum, não usa username na URL 2. `token=` query string — simples para curl/GETs 3. `access_token=` query string — compat OAuth2 consumers 4. HTTP Basic (`username:password`) — cria token, admin 5. HTTP Basic + OTP — quando 2FA habilitado (`X-Gitea-OTP: 123456`) 6. SSH HTTP Signature — chaves SSH registradas, draft-cavage-http-signatures **Token nunca é reexibido** — apare | |:"`name":"meu-token","sha1":"9fcb11..."` — só sha1 na listagem /**/ **Scopes**: formato `:`, ou `all` para tudo. Permissions: activitypub, admin, issue, misc, notification, organization, package, repository, user **Pagination**: `?page=N&limit=N`, header `Link` (`rel=next/last`), `x-total-count` **Sudo (admin)**: adicionar `Sudo: username` header ou param `sudo=username` ### Suggested Action Usar sempre `scopes:["all"]` para automação, guardar token em segredo, preferir header `Authorization: token` sobre query string. ### Metadata - Source: documentation - Tags: gitea, api, auth, token, swagger, devops - Pattern-Key: gitea-api.authentication - Recurrence-Count: 1 --- ## [LRN-20260520-004] gitea-api-endpoints-reference **Logged**: 2026-05-20T12:31:00-03:00 **Priority**: medium **Status**: reference **Area**: devops | api ### Summary Mapa dos endpoints Gitea API v1 por domínio ### Details - **Usuário**: `GET /api/v1/user`, `GET /api/v1/users/:username` - **Repos**: `GET/POST /api/v1/user/repos`, `GET /api/v1/repos/:owner/:repo` - **Issues**: `GET/POST /api/v1/repos/:owner/:repo/issues` - **Pulls**: `GET/POST /api/v1/repos/:owner/:repo/pulls` - **Webhooks**: `GET/POST /api/v1/repos/:owner/:repo/hooks` - **Admin users**: `GET/POST/PUT/DELETE /api/v1/admin/users` - **Swagger**: `https://host/api/swagger` - **OpenAPI JSON**: `https://host/swagger.v1.json` ### Suggested Action Consultar SKILL.md `skills/gitea-api/SKILL.md` antes de usar a API. ### Metadata - Source: documentation - Tags: gitea, api, endpoints, rest - Pattern-Key: gitea-api.endpoints - Recurrence-Count: 1 --- ## [LRN-20260519-001] clawhub.cli_path **Logged**: 2026-05-19T20:39:00-03:00 **Priority**: high **Status**: pending **Area**: config ### Summary Instalar skills do Clawhub com `/var/lib/openclaw/tools/node/npm/bin/clawhub` ### Details O CLI `clawhub` não está no `$PATH` global, mas existe instalado em `/var/lib/openclaw/tools/node/npm/bin/clawhub`. Sempre usar o caminho completo. ### Suggested Action Adicionar symlink ou alias para `clawhub` no PATH do agente, ou sempre usar o caminho completo. ### Metadata - Source: error - Tags: clawhub, cli, path, install - Pattern-Key: clawhub.cli_path --- ## [LRN-20260519-002] clawhub-search-qc **Logged**: 2026-05-19T20:42:00-03:00 **Priority**: medium **Status**: pending **Area**: config ### Summary `clawhub search` com termos muito específicos retorna vazio; usar termos genéricos primeiro ### Details Pesquisas com termos combinados como "programming developer full-stack" retornaram vazio. Termos simples como "autonomous agent" retornaram resultados. Melhor abordar a pesquisa em múltiplas queries curtas. ### Suggested Action Fazer múltiplas searches com termos curtos e depois filtrar manualmente. ### Metadata - Source: error - Tags: clawhub, search, query - Pattern-Key: clawhub.search_strategy --- ## [LRN-20260519-003] biblioteca-compartilhada-libs **Logged**: 2026-05-19T21:30:00-03:00 **Priority**: medium **Status**: pending **Area**: config ### Summary Criar biblioteca inteligente compartilhada em `libs/` para reuso entre projetos ### Details Toda skill instalada tem conhecimento valioso. Centralizar em `libs//`: - skills são extraídas e promovidas para arquivos .md limpos - novos projetos copiam `libs/` como template - o agente consulta `libs/` antes de implementar ### Metadata - Source: best_practice - Tags: biblioteca, reuso, padroes - Pattern-Key: libs.shared_knowledge_base --- ## [LRN-20260519-004] vitest-jsdom-global-mocks **Logged**: 2026-05-19T23:10:00-03:00 **Priority**: high **Status**: applied **Area**: testing ### Summary Vitest jsdom — mocks globais obrigatórios antes de renderizar hooks que usam APIs do navegador ### Details `localStorage`, `navigator.clipboard`, `window.matchMedia` não existem no jsdom puro — mockar em `beforeAll/beforeEach` global. ### Metadata - Source: best_practice - Tags: vitest, jsdom, mocks, testing - Pattern-Key: vitest.jsdom.mocks - Recurrence-Count: 3 → promoted --- ## [LRN-20260519-005] react-testing-library-hooks-async **Logged**: 2026-05-19T23:10:00-03:00 **Priority**: medium **Status**: pending **Area**: testing ### Summary React Testing Library — renderHook + act() para testar hooks assíncronos ### Details Sempre envolver awaits em `act(async () => { ... })`, nunca acessar `result.current` após await diretamente fora de `act()`. ### Metadata - Source: best_practice - Tags: react, testing-library, hooks, async, vitest - Pattern-Key: react.testing-library - Recurrence-Count: 3 → promoted --- ## [LRN-20260520-001] vitest-pure-dom-matchers **Logged**: 2026-05-20T00:52:00-03:00 **Priority**: high **Status**: applied **Area**: testing ### Summary Vitest jsdom puro — matchers nativos DOM funcionam sem `@testing-library/jest-dom` ### Details Usar `.classList.contains()`, `.getAttribute()`, `.textContent`, `container.querySelector()` ao invés de matchers RTL. 56/56 testes verdes, 0 dependências extras. ### Metadata - Source: best_practice - Tags: vitest, jsdom, testing, matchers - Pattern-Key: vitest.pure-dom-matchers - Recurrence-Count: 1 --- ## [LRN-20260520-002] jsdom-fireEvent-writable-value **Logged**: 2026-05-20T00:52:00-03:00 **Priority**: low **Status**: workaround **Area**: testing ### Summary No jsdom, `Object.defineProperty(input, 'value', { writable: true })` permite `fireEvent.change` ### Details Workaround mínimo para testar onChange em inputs no jsdom puro, onde `.value` é readonly. ### Metadata - Source: error - Tags: jsdom, fireEvent, input, value - Pattern-Key: jsdom.fireEvent-change-writable - Recurrence-Count: 1 --- ## [LRN-20260520-006] portainer-api-ptr-token-scope **Logged**: 2026-05-20T17:38:00-03:00 **Priority**: high **Status**: reference **Area**: devops | infra | docker ### Summary Token Portainer com prefixo `ptr_` é um access token — funciona em `/api/status` (200) mas retorna 401 em `/api/endpoints`, `/api/stacks`, `/api/auth` — escopo muito limitado. ### Details Para operações de mutação (criar/gerenciar stacks) pela API Portainer é necessário JWT admin válido obtido via `POST /api/auth` com username+senha. Senha admin do Portainer não estava documentada — usamos `docker stack deploy` via CLI Docker Swarm diretamente como workaround. ### Suggested Action Documentar senha admin do Portainer de forma segura (password manager), ou usar service token JWT com escopo `admin` completo. ### Metadata - Source: error - Tags: portainer, api, token, docker-swarm, jwt - Pattern-Key: portainer.api-ptr-token-scope --- ## [LRN-20260520-007] docker-compose-v3-swarm-labels-and-restart-policy **Logged**: 2026-05-20T17:38:00-03:00 **Priority**: high **Status**: reference **Area**: devops | docker ### Summary docker stack deploy com compose v3.9 no Swarm mode — duas propriedades que não funcionam como esperado: 1. **`restart_policy`** — não é propriedade válida no `deploy` spec; Docker Swarm gerencia restart nativamente (Always) 2. **`deploy.labels`** — não se tornam container labels; labels só são aplicadas via `docker service create --label-add` ou `--label-add` em `docker service update` ### Fix - Remover `restart_policy` do YAML - Aplicar labels necessárias (ex: caddy=) via CLI `docker service update --label-add` ou diretamente no `docker service create` ### Metadata - Source: error - Tags: docker, swarm, compose, labels, restart-policy - Pattern-Key: docker.swarm-compose-v3-gotchas --- ## [LRN-20260520-008] stack-migration-docker-swarm-createmigrate **Logged**: 2026-05-20T17:38:00-03:00 **Priority**: medium **Status**: reference **Area**: devops | docker ### Summary Migração de stack Docker Swarm (trocar de nome/namespace): 1. Criar novos serviços com o nome da stack destino + labels corretas (`docker service create --label-add ...`) 2. Validar que os serviços estão saudáveis 3. Remover a stack antiga (`docker stack rm `) ### Safety notes - Não usar `docker stack deploy --prune` em stack ativa em produção — apaga services não presentes no novo compose - Para imagens locais: usar `--with-registry-auth` ou tag com registry acessível - Sempre validar `docker service ps ` antes de remover a stack antiga ### Metadata - Source: best_practice - Tags: docker, swarm, migration, stack - Pattern-Key: docker.swarm-stack-migration ## LRN-20260520-010 — Stack Portainer API Attachable=false bloqueia deploy **Categoria**: knowledge_gap ### Problema - Rede `public` tem `Attachable: false` - `docker stack deploy` CLI FUNCIONA mesmo assim (Daemon local ignora) - Portainer API BLOQUEIA (Docker Remote API respeita `Attachable=false`) - Erro: `failed to set up container networking: Could not attach to network public: rpc error: code = PermissionDenied desc = network public not manually attachable` ### Decisão tomada Stack `project` fica gerenciada por `docker stack deploy` CLI (Swarm nativo). Portainer controla apenas stacks 1,2,4,6,7,8,9 (com SwarmId preenchido). Stack `project` não é controlável pelo Portainer enquanto `Attachable=false`. ### Para resolver completamente Recriar rede `public` com `--attachable` impactando 19 containers — NÃO recomendado agora.