sqlite-graphrag 1.1.6

Persistent GraphRAG memory for Claude Code, Codex, Cursor, and 27 AI agents — one self-contained ~19 MiB Rust binary, zero daemon. Never re-explain your codebase again. Hybrid retrieval (FTS5 BM25 + cosine similarity + multi-hop graph traversal) surfaces the right memory in milliseconds. Embedding and entity enrichment run as parallel REST calls against your cloud LLM — no fragile headless subprocesses, no ONNX runtime, no model downloads. Soft-delete with full version history, transactional atomic writes, BLAKE3-tracked mutations. OAuth-only: raw API keys ABORT the spawn.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
## Preservação de Env de Custom Provider em Invocação Headless (v1.0.83+)
- O pipeline de invocação headless (`claude_runner`, `codex_spawn`, `ingest_claude`) agora preserva seis env vars de custom-provider ao spawnar subprocessos: `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, `OPENAI_BASE_URL`, `CLAUDE_CODE_ENTRYPOINT`, `DISABLE_TELEMETRY`, `OTEL_EXPORTER_OTLP_ENDPOINT`
- Os três spawners delegam para `apply_env_whitelist(cmd, strict)` de `src/spawn/env_whitelist.rs` em vez de inlinear o array de whitelist. Isso elimina o drift entre os três blocos duplicados de `env_clear` + re-injeção
- O guard OAuth-only em `claude_runner.rs:273`, `codex_spawn.rs:259`, `ingest_claude.rs:282`, `extract/llm_embedding.rs:237-253` permanece inalterado; `ANTHROPIC_API_KEY` e `OPENAI_API_KEY` ainda abortam com `AppError::Validation` (exit 1) e a nova mensagem de erro referencia `ANTHROPIC_AUTH_TOKEN` e `~/.codex/auth.json` como resoluções legítimas
- Nova flag global `--strict-env-clear` / `SQLITE_GRAPHRAG_STRICT_ENV_CLEAR=1` ativa modo estrito que preserva apenas `PATH`. Use em ambientes compliance (PCI-DSS, SOC2, HIPAA) onde encaminhamento de credenciais via env vars é proibido por política
- As 7 flags de endurecimento para `claude -p` (`--strict-mcp-config --mcp-config '{}' --settings '{"hooks":{}}' --dangerously-skip-permissions --output-schema` mais model e prompt) e o conjunto canônico para `codex exec` permanecem inalterados. A mudança no whitelist de env é puramente aditiva no passo de whitelist entre `env_clear()` e a construção das flags canônicas
- Sem telemetria nova: o fix é silencioso. O teste de auditoria no-leak `audit_no_token_leak_in_subprocess_stderr` em `tests/claude_runner_env.rs` garante que o valor literal do token NUNCA aparece em stdout ou stderr mesmo com `RUST_LOG=trace`
- Veja `docs/decisions/adr-0041-preserve-custom-provider-env.pt-BR.md` para a justificativa arquitetural completa
# Invocação Headless — Claude Code, Codex, OpenCode sem MCP e sem Hooks (v1.0.89 — Camada Pre-flight + Hotfixes)

> Como invocar LLMs headless neste projeto sem herdar MCPs ou hooks do ambiente, mantendo o login OAuth de assinatura.

- Versão em inglês deste guia vive em [HEADLESS_INVOCATION.md](HEADLESS_INVOCATION.md)
- Voltar ao [README.md](../README.md) para referência de comandos


## Resumo

## Contrato stdout/stderr e --quiet (v1.1.05)

ADR: [ADR-0065](decisions/adr-0065-v1-1-05-danilo-bugs.pt-BR.md). Suite de regressão: `tests/v1105_danilo_bugs_regression.rs` (nome da suite **v1105**).

- JSON estruturado SEMPRE no stdout; logs de tracing SEMPRE no stderr
- Use `--quiet`/`-q` (global) para suprimir tracing não-erro — útil em pipelines headless que parseiam stdout com `jaq`
- Para envelopes grandes de `deep-research`, prefira `--output PATH` (escrita atômica atomwrite) em vez de redirecionar stdout para arquivo misturado com stderr. Ack no stdout: `written`, `bytes`, `blake3`, `sub_queries_total`, `unique_memories_found`, `elapsed_ms`. Schema: `docs/schemas/deep-research-output-ack.schema.json`
- Queries de token único em `deep-research` expandem para sub-queries com `source: "aspect"` (fan-out multi-ângulo); estratégia manual via `--sub-query-strategy manual --sub-queries-file`
- Em scripts headless, use `graph traverse --fuzzy` quando o nome canônico for desconhecido; sem match exato, exit 4 inclui sugestões
- Prefira `link --from-id`/`--to-id` em automações que só têm IDs; NUNCA passe dígitos puros em `--from`/`--to` com `--create-missing`
- `merge-entities` rejeita self-ref (`--into-id` em `--ids`) antes do DB — útil sob loops zsh/bash malformados
- Nunca use `sqlite-graphrag ... &> arquivo` (redireciona stdout+stderr juntos e contamina o JSON)

```bash
# deep-research headless com saída atômica em arquivo (recomendado para agentes)
OUTDIR=/tmp/graphrag-out
mkdir -p "$OUTDIR"
sqlite-graphrag --quiet \
  --embedding-backend openrouter --embedding-model qwen/qwen3-embedding-8b --embedding-dim 384 \
  deep-research "danilo" --max-sub-queries 7 --k 20 --with-bodies \
  --output "$OUTDIR/research.json" --json
# Parseie o ack no stdout; envelope completo no arquivo
# Facetas manuais opcionais:
# printf '%s\n' 'danilo stack' 'danilo projetos' > "$OUTDIR/subs.txt"
# sqlite-graphrag --quiet deep-research "danilo" \
#   --sub-query-strategy manual --sub-queries-file "$OUTDIR/subs.txt" \
#   --output "$OUTDIR/research.json" --json
```


- Claude Code OAuth sem MCP usa `--strict-mcp-config --mcp-config '{}'`
- Codex OAuth sem MCP usa `codex exec -c mcp_servers='{}'`
- OpenCode OAuth sem MCP usa `OPENCODE_CONFIG_CONTENT` com `enabled` falso por servidor
- A descoberta mais importante: no Claude, a flag `--bare` corta os MCP mas DESLIGA o OAuth. `--bare` passa a exigir chave de API, que aqui é proibida. Por isso NÃO se usa `--bare` quando o login é por assinatura


## Tabela de Comandos OAuth-Safe

| CLI | Comando headless OAuth-safe | Mantém OAuth | Corta MCP | Corta Hooks |
| --- | --- | --- | --- | --- |
| Claude Code | `claude -p "TAREFA" --strict-mcp-config --mcp-config '{}' ...` | sim | sim | sim |
| Codex CLI | `codex exec -c mcp_servers='{}' ...` | sim | sim | N/A |
| OpenCode | `OPENCODE_CONFIG_CONTENT='{...enabled:false...}' opencode run ...` | sim | sim | N/A |


## Claude Code Headless OAuth sem MCP e sem Hooks

### O Que Fazer

Rodar `claude -p` com a config de MCP travada e vazia, e a config de hooks zerada.

### Por Que Fazer

- O `-p` ativa o modo headless de uma tacada só
- O `--strict-mcp-config` manda ignorar TODA config de MCP do ambiente
- O `--mcp-config '{}'` entrega uma lista vazia de servidores
- O `--settings '{"hooks":{}}'` desliga os hooks naquela chamada específica
- A combinação garante zero MCP e zero hooks no ar, mantendo o login por assinatura (OAuth Pro ou Max)

### Atualização v1.0.79 — O Isolamento Real É `CLAUDE_CONFIG_DIR` Vazio

- A issue #10787 de `anthropics/claude-code` documenta que `--strict-mcp-config` e `--mcp-config` são silenciosamente IGNORADAS pelo upstream
- O único mecanismo que o upstream honra é `CLAUDE_CONFIG_DIR` apontando para um diretório vazio
- Desde a v1.0.79 (G42/S6), o pipeline de embedding da CLI usa `CLAUDE_CONFIG_DIR` vazio POR PADRÃO: honra `SQLITE_GRAPHRAG_CLAUDE_EMPTY_CONFIG_DIR`, senão um diretório gerenciado `~/.local/state/sqlite-graphrag/claude-empty-config` (modo 0700, copia `.credentials.json` quando presente)
- Um `~/.claude` populado custava ~223k tokens de cache-creation por chamada (~40-50s); o config dir vazio derruba para ~10-15s
- As flags abaixo continuam sendo passadas por defesa em profundidade, mas NÃO confie nelas como isolamento

### Atualização v1.0.91 — Isolamento Automático de CWD via `apply_cwd_isolation()`

- Desde a v1.0.91, TODOS os 10 sites de spawn de subprocessos LLM chamam `apply_cwd_isolation()` de `src/spawn/mod.rs`
- Esta função define `current_dir(temp_dir)` — o CWD do subprocesso é um diretório limpo `/tmp/sqlite-graphrag-spawn-{PID}/` SEM `.mcp.json` em nenhum ancestral
- Também define `CLAUDE_CONFIG_DIR=temp_dir` — isolando o subprocesso da configuração MCP user-level de `~/.claude/`
- O workaround manual `SQLITE_GRAPHRAG_SKIP_PREFLIGHT=1 CLAUDE_CONFIG_DIR=/tmp/graphrag-empty-config` NÃO É MAIS NECESSÁRIO para operação normal (mantido como override de emergência)
- Diretórios de spawn são limpos automaticamente ao final do processo via `cleanup_spawn_dir()` em `src/main.rs` (GAP-SPAWN-002) — `remove_dir()` não-recursivo, seguro para diretórios não-vazios
- A env var `SQLITE_GRAPHRAG_CLAUDE_EMPTY_CONFIG_DIR` da v1.0.79 e o diretório gerenciado `~/.local/state/sqlite-graphrag/claude-empty-config` permanecem funcionais mas são suplantados pelo isolamento automático de CWD

### Atualização v1.0.93 — Backend de Embedding OpenRouter Dispensa Subprocesso

- Desde a v1.0.93, embedding pode usar a API REST do OpenRouter em vez de spawnar um subprocesso LLM headless
- Use `--embedding-backend openrouter --embedding-model MODELO` para rotear embedding via `POST /api/v1/embeddings`
- Isso elimina o cold-start do subprocesso (~200ms de chamada API vs 15-20s de spawn de subprocesso por embedding)
- O caminho OpenRouter usa `reqwest+rustls-tls` diretamente — sem `claude -p`, sem `codex exec`, sem isolamento de CWD necessário
- O enforcement OAuth-only NÃO se aplica ao OpenRouter — ele usa sua própria `OPENROUTER_API_KEY` / `--openrouter-api-key`
- O spawn de subprocesso headless permanece inalterado para embedding baseado em LLM (`--embedding-backend llm`) e para operações de `enrich` rodadas com `--mode claude-code|codex|opencode`
- Desde a v1.0.95 (ADR-0054), `enrich --mode openrouter` também dispensa o subprocesso: a etapa JUDGE roda via endpoint REST `/chat/completions` do OpenRouter (`reqwest+rustls-tls`), sem spawn de `claude -p` / `codex exec` / `opencode run` e sem isolamento de CWD. O pipeline SCAN→JUDGE→PERSIST permanece inalterado; só o transporte do JUDGE muda.
- A flag `--enrich-after` no `ingest` ainda spawna um subprocesso headless para a fase de enrich quando o modo de enrich é uma CLI local; com `--mode openrouter` a fase de enrich permanece sem subprocesso
- Veja ADR-0052 (embedding OpenRouter) e ADR-0054 (JUDGE do enrich via OpenRouter) para a justificativa arquitetural completa

### Atualização v1.0.95 — JUDGE do Enrich via OpenRouter Dispensa Subprocesso

- `enrich --mode openrouter` roteia a etapa JUDGE para `POST /api/v1/chat/completions` — sem subprocesso de CLI local
- `--openrouter-model` é OBRIGATÓRIA com `--mode openrouter` (SEM default; omiti-la → exit 1 antes de qualquer chamada de rede)
- `--openrouter-api-key` lê da env `OPENROUTER_API_KEY` ou de `config add-key --provider openrouter`; `--openrouter-timeout` tem default de 300s; `--openrouter-base-url` é opcional
- A requisição usa `response_format` `json_schema` com `strict: true` e `provider.require_parameters: true`; `reasoning.enabled: false` com fallback reasoning-mandatory de uma retentativa; `usage.cost` é lido da resposta
- Trade-off: OAuth zero-token (modos CLI locais) vs tokens cobrados na `OPENROUTER_API_KEY` (modo OpenRouter); o caminho JUDGE do OpenRouter em si não exige migração, mas a v1.1.04 avança o schema do banco principal para v16 (a tabela `entity_connect_seen` da V016, exigida apenas quando você rodar depois `enrich --operation entity-connect`)

```bash
# JUDGE do enrich headless via REST OpenRouter (sem subprocesso, sem isolamento de CWD)
export OPENROUTER_API_KEY="sk-or-v1-sua-chave-aqui"
sqlite-graphrag enrich --operation memory-bindings \
  --mode openrouter --openrouter-model "qwen/qwen3-235b-a22b" --json
```

### Atualização v1.0.96 — Convergência do Backlog e Status Read-Only da Fila (ADR-0055)

- `enrich --until-empty` substitui o loop bash externo de retry na invocação headless: um único processo roda o loop interno scan→drain até a fila não ter mais itens elegíveis ou `--max-runtime <SECONDS>` (default 3600) expirar. A fila dead-letter garante que o conjunto vivo decresce estritamente — falhas transientes reagendam `next_retry_at` com backoff, um item vira `dead` após `--max-attempts` (padrão 8) retries transientes ou na primeira falha dura, e linhas `dead` são excluídas do dequeue.
- `enrich --status --json` é a sonda read-only para hooks e timers: reporta as contagens da fila (`unbound_backlog`, `scan_backlog` por operação, `queue_pending/done/failed/dead/skipped`, `eligible_now`, `waiting`) e NÃO chama o LLM e NÃO adquire o singleton por namespace; o `scan_backlog` (GAP-SG-77, v1.1.0) é o backlog real do banco por operação que um scan enfileiraria — elimina o falso `pending=0` para `entity-descriptions`/`body-enrich`/`re-embed`, e o `state` deriva o `pending-scan` dele. Um timer cron ou systemd pode fazer poll sem disputar com um `enrich` em execução.
- `enrich --prune-dead-orphans --json` é um inspetor read-only complementar (sem LLM, sem singleton): deleta linhas dead-letter (`status='dead'`, `item_type='memory'`) cujo nome de memória não existe mais no banco principal, mutando apenas o sidecar `.enrich-queue.sqlite`; linhas dead de entidade não são tocadas. Use-o em scripts de manutenção headless para limpar acúmulo de dead-letter órfão de memórias renomeadas ou purgadas após o enqueue (ADR-0058, GAP-SG-66, v1.0.97).
- `enrich --prune-dead-entity-orphans --json` (v1.1.02, ADR-0062) é a contraparte para chaves de entidade: deleta linhas dead-letter com `item_type='entity'`, e é mutuamente exclusivo com `--prune-dead-orphans`. Rode ambos em sequência para uma varredura completa de órfãos após um upgrade que renomeou/fundiu/purgou entidades.
- `--rest-concurrency <N>` (clamp 1..=16, default 8) define o fan-out REST in-flight para embedding `--mode openrouter`; aumente-o para vazão OpenRouter. É distinta de `--llm-parallelism` (que limita subprocessos LLM locais) e de `--max-attempts` (o orçamento de retries).

```bash
# Drain headless do backlog — sem while-loop externo, sem subprocesso para OpenRouter
export OPENROUTER_API_KEY="sk-or-v1-sua-chave-aqui"
sqlite-graphrag enrich --operation memory-bindings \
  --mode openrouter --openrouter-model "qwen/qwen3-235b-a22b" \
  --until-empty --max-runtime 1800 --max-attempts 8 --rest-concurrency 8 --json

# Sonda de hook/timer — inspecionar a fila sem spawnar o LLM nem adquirir o singleton
sqlite-graphrag enrich --status --json | jaq '{eligible_now, waiting, dead: .queue_dead}'
```

### Por Que NÃO Usar `--bare`

- O `--bare` também corta MCP, hooks, skills, plugins e auto memory
- MAS o `--bare` desativa o OAuth e o keychain (issue #39069 de `anthropics/claude-code`)
- Com `--bare`, o Claude exige `ANTHROPIC_API_KEY`, que é proibido neste projeto
- Para manter OAuth, o caminho certo é `--strict-mcp-config`, nunca `--bare`

### Como Fazer

```bash
claude -p "SUA TAREFA AQUI" \
  --strict-mcp-config \
  --mcp-config '{}' \
  --dangerously-skip-permissions \
  --settings '{"hooks":{}}' \
  --model claude-sonnet-4-6 \
  --max-turns 8 \
  --output-format json
```

### O Que Cada Pedaço Faz

- `--strict-mcp-config` ignora MCP de settings global e de projeto
- `--mcp-config '{}'` fornece a lista vazia que zera os servidores
- `--dangerously-skip-permissions` evita travar pedindo confirmação (modo `bypassPermissions`)
- `--settings '{"hooks":{}}'` desliga os hooks naquela chamada específica
- `--model claude-sonnet-4-6` escolhe o modelo sem depender de variável de ambiente
- `--max-turns 8` limita as voltas do agente como rede de segurança contra loop infinito
- `--output-format json` entrega saída fácil de parsear com `jaq`

### Como Garantir o OAuth

- Fazer login uma vez com a conta Pro ou Max antes de automatizar (`claude auth login`)
- NÃO definir `ANTHROPIC_API_KEY` no ambiente da chamada
- NÃO usar `--bare`
- Sem a variável e sem `--bare`, o Claude usa a sessão logada via OAuth

### Ressalva do Bug Conhecido

- Issue #14490 do `anthropics/claude-code` documenta que `--strict-mcp-config` NÃO sobrescreve a lista `disabledMcpServers` armazenada em `~/.claude.json`
- Para ambiente limpo, garantir que `~/.claude.json` não contém o servidor em `disabledMcpServers` ou usar `--bare` somente em ambiente controlado com `ANTHROPIC_API_KEY` (cenário explicitamente PROIBIDO neste projeto)
- A solução robusta é combinar `--strict-mcp-config --mcp-config '{}'` e garantir que o servidor não está em `disabledMcpServers` em `~/.claude.json`


## Codex CLI Headless OAuth sem MCP

### O Que Fazer

Rodar `codex exec` zerando a tabela de servidores MCP do config.

### Por Que Fazer

- O `codex exec` é o modo não interativo feito para scripts
- Ele escreve só a mensagem final no stdout e progresso no stderr
- O override `-c mcp_servers='{}'` substitui a tabela inteira por vazia
- Assim nenhum servidor MCP do `config.toml` sobe naquela chamada

### Como Fazer

```bash
codex exec \
  --model gpt-5.5 \
  -c mcp_servers='{}' \
  --sandbox workspace-write \
  --ask-for-approval never \
  "SUA TAREFA AQUI"
```

### Alternativa Mais Agressiva

- Usar `--ignore-user-config` para nem ler o `config.toml` do usuário
- Isso zera MCP junto com tudo mais que estiver no config
- O login OAuth fica salvo em `auth.json`, que é arquivo separado
- Por isso o `--ignore-user-config` NÃO derruba o login

```bash
codex exec --model gpt-5.5 --ignore-user-config --sandbox workspace-write "SUA TAREFA AQUI"
```

### O Que Cada Pedaço Faz

- `-c mcp_servers='{}'` zera só os MCP e preserva modelo e resto do config
- `--ignore-user-config` é o corte total quando você quer ambiente limpo
- `--sandbox workspace-write` libera edição de arquivos sem rede
- `--ask-for-approval never` roda sem pausar pedindo permissão

### Como Garantir o OAuth

- Rodar `codex login` uma vez para o fluxo do navegador com o ChatGPT
- Em máquina remota ou sem navegador, usar `codex login --device-auth`
- NÃO definir `OPENAI_API_KEY` no ambiente da chamada
- O login fica salvo em `~/.codex/auth.json` e o `codex exec` reaproveita a sessão

### Ressalva do Bug Antigo

- Versões antigas do Codex (0.33.0) instaladas via Homebrew não liam `[mcp_servers]` corretamente
- Issue #3441 do repositório `openai/codex` confirma que o fix está em 0.34.0+
- Validar versão com `codex --version` antes de usar o override `-c mcp_servers='{}'`


## OpenCode Headless sem MCP

### A Diferença Honesta

- O OpenCode NÃO tem uma flag única de CLI para desligar MCP
- O Claude tem `--strict-mcp-config` e o Codex tem `-c mcp_servers='{}'`
- O OpenCode controla MCP só pela config em JSON
- As configs do OpenCode são somadas, não trocadas, então é preciso desligar por servidor

### O Que Fazer

- Descobrir os nomes dos servidores ativos com `opencode mcp list`
- Desligar cada um com `enabled: false` no config

### Por Que Fazer

- O `opencode run` é o modo headless que recebe o prompt e devolve resultado
- Como a config é somada, apagar a chave não basta para remover o servidor
- Setar `enabled` falso com o mesmo nome sobrescreve e desliga aquele MCP
- O override de runtime via `OPENCODE_CONFIG_CONTENT` evita mexer nos arquivos do projeto

### Como Fazer — Passo 1 Listar Servidores Ativos

```bash
opencode mcp list
```

### Como Fazer — Passo 2 Rodar Headless Desligando Cada Servidor

```bash
OPENCODE_CONFIG_CONTENT='{"mcp":{"nome-do-server-1":{"enabled":false},"nome-do-server-2":{"enabled":false}}}' \
  opencode run --model anthropic/claude-sonnet-4-5 "SUA TAREFA AQUI"
```

### Alternativa Permanente

- Editar o `opencode.json` e marcar cada MCP com `enabled` falso
- Vale quando você nunca quer aquele servidor em execução automática

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "nome-do-server-1": { "enabled": false },
    "nome-do-server-2": { "enabled": false }
  }
}
```

### O Que Cada Pedaço Faz

- `opencode mcp list` mostra nomes e status de conexão dos servidores
- `OPENCODE_CONFIG_CONTENT` injeta config inline com alta precedência
- `enabled` falso por servidor é o que de fato impede a subida do MCP
- `--model` escolhe o modelo no formato `provedor/modelo`

### Como Garantir o OAuth

- Rodar `opencode auth login` uma vez e escolher o provedor
- A credencial fica salva em `auth.json` na pasta de dados do OpenCode
- O `opencode run` reaproveita essa credencial nas chamadas seguintes


## Login OAuth por CLI

- Claude: login na sessão via `claude auth login`. NÃO usar `--bare` para preservar OAuth
- Codex: `codex login` ou `codex login --device-auth` (sem navegador)
- OpenCode: `opencode auth login`


## Modo Headless por CLI

- Claude: `claude -p`
- Codex: `codex exec`
- OpenCode: `opencode run`



### Atualização v1.1.06 — entity-connect headless em namespaces grandes (ADR-0066)

Registro de decisão: [ADR-0066](decisions/adr-0066-v1-1-06-entity-connect-scan.pt-BR.md). Suite de regressão: `tests/v1106_entity_connect_scan_regression.rs` (nome **v1106**).

- Fecha **GAP-ENTITY-CONNECT-SCAN-CARTESIAN**: `enrich --operation entity-connect` (e `cross-domain-bridges`) headless no `global` grande não trava mais a 100% de CPU antes de `phase: scan`. O scan de pares é O(k) (coocorrência + hub×ilha), não cartesiano O(n²).
- Chaves da fila `pair:{id1}:{id2}` com `item_type=entity_pair`; drain por chave primária (sem re-scan por item). GAP-002 `entity_connect_seen` permanece em vigor.
- **Wall-clock do primeiro scan** coberto por `--max-runtime` e teto soft de 120s via `InterruptHandle`. Timeout → `AppError::Timeout` exit **1**. Orquestradores NÃO DEVEM tratar timeout de scan como exit **75** (singleton/slot).
- NDJSON para hooks: espere `phase: "scan_start"` **antes** do SQL (`operation`, `entities_in_namespace`, `backlog_degree0_proxy`), depois `scan` / `scan_meta` (`pairs_enqueued_this_scan`, `scan_elapsed_ms`). Não equacione os dois campos de backlog.
- Prefira dry-run antes de jobs longos `--until-empty` em grafos densos.
- Sem migração de schema na v1.1.06 (permanece v16). Pin `=1.1.6`.

```bash
# Dry-run headless deve terminar rápido e emitir scan_start (sem hang cartesiano)
sqlite-graphrag enrich --operation entity-connect --dry-run --json --limit 50 \
  --mode openrouter --openrouter-model deepseek/deepseek-v4-flash:nitro

# Convergência longa: --max-runtime também cobre o PRIMEIRO scan
sqlite-graphrag enrich --operation entity-connect --until-empty --max-runtime 600 \
  --mode openrouter --openrouter-model deepseek/deepseek-v4-flash:nitro --json
```

### Atualização v1.1.04 — Estabilidade do deep-research + Convergência do entity-connect (ADR-0064)

- GAP-001: o `deep-research` não entra mais em panic com "Cannot start a runtime from within a runtime" quando invocado em modo headless (agent harnesses, runners de CI, jobs agendados). O entry point síncrono `deep_research::run` agora computa os embeddings por sub-query ANTES de construir seu runtime Tokio dedicado via o novo helper `compute_sub_embeddings`, e os três caminhos de embedding OpenRouter em `embedder.rs` (single, batch serial, fan-out JoinSet) adotam o padrão canônico de reentrada `Handle::try_current` + `block_in_place` já usado pelo path batch. O `ingest_opencode` também recebeu o guard. Para orquestradores headless isso significa que jobs `deep-research --with-bodies` de longa duração que antes crashavam no meio agora completam de forma confiável.
- GAP-002: o `entity-connect` agora converge em loops headless de longa duração. A nova tabela `entity_connect_seen` (migração V016, schema do banco principal v15 → v16) registra o veredito do LLM (`related`/`none`) por par avaliado; o scanner `scan_isolated_entity_pairs` exclui pares já avaliados e prioriza entidades hub; e o `call_entity_connect` persiste o veredito nos dois ramos. Combinado com `--until-empty --max-runtime`, um job headless `enrich --operation entity-connect` agora atinge `eligible_remaining == 0` em vez de re-avaliar infinitamente os mesmos pares rejeitados. Rodar `migrate --json` uma vez na primeira abertura é OBRIGATÓRIO antes da primeira invocação do `entity-connect`.


### Atualização v1.1.03 — Recuperação de Claims Stale no enrich Headless de Longa Duração

- Orquestradores headless (agent harnesses, runners de CI, timers do systemd) frequentemente enviam SIGINT, SIGTERM e ocasionalmente SIGKILL para jobs `enrich --until-empty` de longa duração
- SIGKILL NÃO é capturável — o sidecar `.enrich-queue.sqlite` pode ficar com linhas presas em `status='processing'` sob o PID morto
- Desde v1.1.03 (ADR-0063, Bug 4), o sidecar da fila ganha uma coluna `claimed_at` INTEGER e o worker do enrich emite um heartbeat por item (`UPDATE queue SET claimed_at = unixepoch() WHERE id = ?`)
- Em CADA startup do enrich, o worker chama `reset_stale_processing_claims(conn, 1800)` — itens com `status='processing' AND claimed_at < unixepoch() - 1800` são devolvidos para `pending` e `claimed_at = NULL`
- O threshold de 1800 segundos (30 minutos) é o padrão; combinado com o heartbeat ele cobre qualquer job que pare de progredir por meia hora
- Para reset manual (ex.: após um incidente kill -9 conhecido), a nova flag `enrich --reset-stale-claims --json` descarrega claims stale sem rodar o loop completo de scan→drain
- SIGTERM (capturável) é tratado pelo path gracioso existente do `signals::handler`; apenas SIGKILL depende da recuperação baseada em timestamp
- Sem nova variável de ambiente, sem telemetria — a recuperação é silenciosa e idempotente

```bash
# Forçar reset de claims stale após um incidente kill -9 conhecido (sem scan, sem LLM)
sqlite-graphrag enrich --reset-stale-claims --json

# enrich headless normal — claims stale são auto-recuperados no startup
OPENROUTER_API_KEY="$KEY" sqlite-graphrag enrich --operation memory-bindings \
  --mode openrouter --openrouter-model "qwen/qwen3-235b-a22b" \
  --until-empty --max-runtime 1800 --json
```

## Atualização v1.0.80 — Resiliência de SHUTDOWN e a Receita de Bypass em 3 Camadas

A v1.0.80 (ADR-0034) endurece o handler em `src/signals.rs` para que
o cenário de processo órfão que a auditoria G42/C2 identificou
não dispare mais `SIGABRT` em `BrokenPipe`. O terceiro Ctrl-C
consecutivo sai com código 130 e **ZERO I/O**, casando com o
contrato abaixo.

Para jobs longos de embedding que o harness do agente (ou qualquer
orquestrador em background) pode matar via SIGINT, use a receita
de bypass em 3 camadas. As 3 camadas são independentes e a receita
compõe aditivamente:

```bash
# Camada 1 — PATH: roteia o subprocesso LLM via o mock-llm no CI
export PATH="$PWD/tests/mock-llm:$PATH"

# Camada 2 — env: diz ao embedder para ignorar a checagem de SHUTDOWN
export SQLITE_GRAPHRAG_IGNORE_SHUTDOWN=1

# Camada 3 — grupo de processos: desanexa a CLI do pgroup do harness
setsid -w timeout 600 \
  sqlite-graphrag remember --graph-stdin < payload.json
```

- **Camada 1 (PATH)**: roteia qualquer `claude -p` ou `codex exec`
  spawned via a mock CLI determinística commitada em
  `tests/mock-llm/`. O subprocesso LLM real é desviado; SIGINT não
  consegue matar um subprocesso que não existe. É a camada mais
  barata e o default certo em CI.
- **Camada 2 (env)**: faz o `if should_obey_shutdown()` do embedder
  curto-circuitar para `true`, então o braço de cancelamento do
  `tokio::select!` é descartado e o batch roda até a conclusão
  mesmo se o cancellation token já estiver cancelled. Zero
  overhead em produção porque a leitura da env é um único
  `std::env::var` por chamada de `should_obey_shutdown()`, não
  em hot path.
- **Camada 3 (setsid)**: dá à CLI seu próprio grupo de processos via
  `setsid -w`, então SIGINT do harness pai não se propaga para o
  filho. `timeout` adiciona um teto rígido de wall-clock (binário
  Rust `timeout-cli` v0.1.0, somente inteiros em segundos —
  `600` é 10 minutos; não passe `10m`).

A receita é agora a referência canônica para qualquer harness de
agente rodando jobs longos de embedding em background. O bypass é
explicitamente opt-in: código de produção NUNCA deve chamar
`try_reset_shutdown()`, e a env var NUNCA deve ser setada em
produção. Tests e invocações de auditoria são os únicos
consumidores válidos.

Se a execução for interrompida entre as camadas, o arquivo SQLite
permanece consistente (WAL, commit atômico, sem escritas
parciais), e `restore` ou `enrich --operation re-embed --resume`
podem retomar a partir da última memória bem-sucedida.

## Camada de Validação Pre-Flight em Invocação Headless (v1.0.87, ADR-0045, GAP-META-005)
- O módulo `src/spawn/preflight.rs` (≥200 linhas, 7 guards, 15 testes unitários) porta todo spawn de subprocesso LLM ANTES do fork
- Os 7 guards em ordem: `check_argv_size`, `check_binary_exists`, `check_mcp_config_inline`, `check_mcp_config_path`, `check_walkup_mcp_json`, `check_output_buffer`, `check_claude_config_dir`
- Falhas retornam `AppError::PreFlightFailed(PreFlightError)` com `exit_code() == 16` (`EX_CONFIG`, `is_permanent() == true`)
- A variante `McpConfigInlineJsonRejected` (Bug 2 do GAP-META-005) é crítica em invocação headless: Claude Code 2.1.177 rejeita `--mcp-config '{}'` literal. O preflight substitui automaticamente por tempfile com `{"mcpServers":{}}`
- A variante `WalkUpMcpJsonInvalid` (Bug 5) detecta `.mcp.json` inválido em diretórios ancestrais do CWD — walk-up de até 16 níveis via `std::path::Path::ancestors()`
- A variante `ArgvExceedsArgMax` (Bug 3) protege contra `E2BIG` pós-fork para corpos de memória grandes. Threshold: `ARG_MAX - 4096` bytes (margem de 4 KB para env vars do kernel)
- A variante `BinaryNotFound` verifica que `claude` ou `codex` está em PATH antes do fork. Usa `which::which` em POSIX e `where` em Windows
- Bypass em emergências: `SQLITE_GRAPHRAG_SKIP_PREFLIGHT=1` desabilita todos os 7 guards. Bypass reverte para `Command::spawn()` direto e herda todas as 5 classes BUG do GAP-META-005
- O preflight compartilha o helper `apply_env_whitelist` (ADR-0041) — ordem de execução: env_clear primeiro, depois preflight
- Cada spawner adiciona uma única linha antes de `cmd.spawn()`: `preflight_check(&PreFlightArgs { ... }).map_err(|e| AppError::PreFlightFailed(e))?`
- Telemetria: `tracing::info!(event = "preflight_passed", spawner = %name, argv_bytes = total)` em sucesso; `tracing::warn!(event = "preflight_failed", spawner = %name, error = %e)` em falha
- Veja `docs/decisions/adr-0045-preflight-validation-layer.md` (en + pt-BR) para a justificativa arquitetural completa

## Hotfixes BUG-11/12/13 em Invocação Headless (v1.0.88, ADR-0046, ADR-0047)
- **BUG-11 (CRÍTICO)**: falha pre-flight em `extract/llm_embedding.rs:563-565` não propagava para `remember`, que silenciosamente persistia a memória com `backend_invoked: "none"` e zero chunks. Corrigido com `embed_via_backend_strict`. Repro: `CLAUDE_CONFIG_DIR=/tmp/bad-config-with-mcp remember --name X --type note --description x --body y` retorna exit 11 + envelope JSON de erro
- **BUG-12 (MÉDIO)**: enforço OAuth-only emitia 2 linhas stderr idênticas (uma de `tracing::error!`, uma de `eprintln!`). Corrigido removendo `eprintln!` duplicado em `src/output.rs`. Teste: `oauth_stderr_emits_single_line_v1088`. Repro: `ANTHROPIC_API_KEY=sk-test /path/bin/sqlite-graphrag init` agora emite 1 linha stderr (eram 2)
- **BUG-13 (MÉDIO)**: `link --create-missing` bypassava validação de nome de entidade. Corrigido validando ANTES de normalizar em `src/commands/link.rs`. 8 testes em `tests/entity_validation_integration.rs`
- Nova variante `AppError::PreFlightFailed(PreFlightError)` com `exit_code() == 16` e `is_permanent() == true`. Substitui os 3 spawners chamando `std::process::exit(16)` diretamente
- Veja `docs/decisions/adr-0046-preflight-remediation.md` e `adr-0047-stderr-deduplication.md` (en + pt-BR)

## Schema Drift e Flag Parity para Agentes Headless (v1.0.89, ADR-0048, ADR-0049)
- `health.schema.json` regenerado via `schemars 0.8` derive macro. 17 novos campos adicionados. `additionalProperties: true` (política Must-Ignore por RFC 7493 I-JSON)
- Agentes que validam resposta de `health --json` devem migrar de `additionalProperties: false` (strict) para Must-Ignore para receber benefícios de evolução de schema
- `--db <PATH>` agora aceito em `embedding status`, `embedding list`, `embedding abandon`, `pending list`, `pending show` — operadores headless podem apontar para múltiplos bancos sem flag global
- `codex-models --json` retorna envelope JSON `{"action":"codex_models","count":N,"default":"...","models":[...]}`
- `migrate --dry-run --json` reporta migrações pendentes sem aplicar. Adicionado `--confirm` para exigir confirmação literal antes de apply
- `ingest --auto-describe` (padrão true) extrai descrição da primeira linha significativa do corpo. Substitui a antiga `"ingested from <path>"` genérica
- `health --namespace <NS> --json` filtra contagens para um único namespace — útil em ambientes multi-tenant
- Binário medido em 15.323.128 bytes (14.6 MiB), dentro de 1 MiB do documentado em `Cargo.toml:6`. Drift viral "6 MB" eliminado
- 1877 testes passando (843 lib + 1013 integração + 21 doc)

## Atualização v1.0.93 — Backend de Embedding OpenRouter
- Nova flag `--embedding-backend openrouter` habilita embedding via REST API sem subprocesso LLM
- Elimina overhead de cold-start: ~200ms por embedding vs 15s com subprocesso
- Requer variável de ambiente `OPENROUTER_API_KEY` ou flag `--openrouter-api-key`
- Requer `--embedding-model MODEL` (sem padrão — o usuário deve especificar)
- Funciona com todos os 8 comandos de embedding no modo headless
- Exemplo de invocação headless:

```bash
OPENROUTER_API_KEY="$KEY" sqlite-graphrag \
  --embedding-backend openrouter \
  --embedding-model "qwen/qwen3-embedding-8b" \
  ingest ./docs --pattern "*.md" --recursive --json
```

## Atualização v1.0.90 — OpenCode como Terceiro Backend LLM (ADR-0051)

A v1.0.90 adiciona o OpenCode como terceiro backend LLM para pipelines
de embedding, ingestão e enriquecimento. A prioridade de auto-detect
agora é `codex > claude > opencode > none`. A cadeia de fallback
padrão é `codex,claude,opencode,none`.

### sqlite-graphrag com backend opencode

```bash
# Forçar backend opencode com modelo específico
sqlite-graphrag --llm-backend opencode --llm-model opencode/big-pickle \
  remember --name example --type note --body "text" --json

# Ingestão com extração opencode
sqlite-graphrag ingest ./docs --mode opencode --recursive --json

# Enriquecimento com opencode
sqlite-graphrag enrich --operation memory-bindings --mode opencode --json
```

### Novas variáveis de ambiente para opencode

- `SQLITE_GRAPHRAG_OPENCODE_BINARY` — sobrescreve o caminho do binário opencode
- `SQLITE_GRAPHRAG_OPENCODE_MODEL` — seleciona o modelo opencode para extração
- `SQLITE_GRAPHRAG_OPENCODE_EMBED_MODEL` — seleciona o modelo opencode para embedding
- Precedência: `OPENCODE_EMBED_MODEL > OPENCODE_MODEL > default opencode/big-pickle`
- Correção de contaminação cruzada (BUG-AUDIT-001): resolução de modelo opencode NÃO faz fallback para `SQLITE_GRAPHRAG_LLM_MODEL`

## Atualização v1.0.89 — Propagação de Flags LLM e Seleção de Modelo (ADR-0050)

A v1.0.89 corrige uma classe crítica de bugs de flag morta: 7 flags
globais de CLI eram aceitas pelo clap mas nunca propagadas para os
módulos internos de embedding. Todas as 7 agora funcionam via CLI ou
variável de ambiente.

### Flags globais novas e corrigidas

- `--llm-model <MODEL>` / `SQLITE_GRAPHRAG_LLM_MODEL` — seleciona o
  modelo de embedding. Padrões: `gpt-5.5` (codex), `claude-sonnet-4-6`
  (claude). Sobrescreve as variáveis por backend
  `SQLITE_GRAPHRAG_CODEX_EMBED_MODEL` e
  `SQLITE_GRAPHRAG_CLAUDE_EMBED_MODEL`
- `--llm-backend <auto|codex|claude|none>` /
  `SQLITE_GRAPHRAG_LLM_BACKEND` — seleciona qual CLI spawna o
  subprocesso de embedding. `auto` (padrão) sonda o PATH: codex
  primeiro, depois claude
- `--codex-binary <PATH>` / `SQLITE_GRAPHRAG_CODEX_BINARY` —
  sobrescreve a localização do binário codex (novo na v1.0.89;
  `--claude-binary` existe desde a v1.0.82)
- `--llm-fallback <chain>` / `SQLITE_GRAPHRAG_LLM_FALLBACK` — cadeia
  de fallback quando o backend primário falha (padrão:
  `codex,claude,none`)
- `--skip-embedding-on-failure` /
  `SQLITE_GRAPHRAG_SKIP_EMBEDDING_ON_FAILURE` — persiste a memória sem
  embedding quando o LLM falha (exit 0 em vez de exit 11)
- `--llm-max-host-concurrency <N>` /
  `SQLITE_GRAPHRAG_LLM_MAX_HOST_CONCURRENCY` — limita os subprocessos
  LLM concorrentes em todo o host
- `--llm-slot-wait-secs <N>` / `SQLITE_GRAPHRAG_LLM_SLOT_WAIT_SECS` —
  segundos para esperar por um slot livre antes de falhar
- `--llm-slot-no-wait` / `SQLITE_GRAPHRAG_LLM_SLOT_NO_WAIT` — falha
  imediatamente se nenhum slot estiver disponível

### BoolishValueParser para env vars booleanas

Flags booleanas com `env = "SQLITE_GRAPHRAG_*"` agora aceitam `1`,
`yes`, `on`, `true` (e `0`, `no`, `off`, `false`). Antes só
`true`/`false` eram aceitos, causando exit 2 para scripts que setavam
`SQLITE_GRAPHRAG_SKIP_EMBEDDING_ON_FAILURE=1`.

### Invocação headless com modelo explícito

```bash
# Claude com modelo explícito
claude -p "SUA TAREFA" \
  --model claude-sonnet-4-6 \
  --strict-mcp-config --mcp-config '{}' \
  --dangerously-skip-permissions \
  --settings '{"hooks":{}}' \
  --output-format json

# Codex com modelo explícito
codex exec \
  --model gpt-5.5 \
  -c mcp_servers='{}' \
  --sandbox workspace-write \
  --ask-for-approval never \
  "SUA TAREFA"
```

### sqlite-graphrag com override de backend e modelo

```bash
# Força o backend claude com modelo específico
sqlite-graphrag --llm-backend claude --llm-model claude-sonnet-4-6 \
  remember --name example --type note --body "text" --json

# Força o backend codex com modelo específico
sqlite-graphrag --llm-backend codex --llm-model gpt-5.5 \
  recall "query" --k 5 --json

# Pula o embedding em caso de falha (persiste a memória sem vetor)
sqlite-graphrag --skip-embedding-on-failure \
  remember --name resilient --type note --body "text" --json
```


## Referências Externas Validadas

### Claude Code

- `code.claude.com/docs/en/headless` — modo headless e exit codes claros
- `amux.io/guides/claude-code-headless/` — guia completo de self-hosting headless (2026)
- `github.com/anthropics/claude-code/issues/39069` — `--bare` mode skips OAuth/keychain, unusable para OAuth-only
- `computingforgeeks.com/claude-code-cheat-sheet/` — cheat sheet com `--mcp-config` e `--strict-mcp-config`
- `github.com/anthropics/claude-code/issues/14490` — `--strict-mcp-config` não sobrescreve `disabledMcpServers`

### Codex CLI

- `developers.openai.com/codex/cli/reference` — referência canônica de CLI options
- `deepwiki.com/openai/codex/6.1-mcp-server-configuration` — MCP server config no `config.toml`
- `ofox.ai/blog/codex-cli-config-toml-deep-dive/` — cada setting do `config.toml` explicado
- `github.com/openai/codex/issues/3441` — bug de `[mcp_servers]` não funcionar em versão antiga do Codex

### OpenCode

- `opencode.ai/docs/mcp-servers/` — controle de MCP via `enabled: false` por servidor
- `open-code.ai/en/docs/config` — referência de `opencode.json` com providers, models, MCP
- `computingforgeeks.com/opencode-cli-cheat-sheet/` — cheat sheet com flags headless e MCP