# Troubleshooting

# Troubleshooting

> _Dernière mise à jour : 2026-05-10_

Pièges connus déjà rencontrés, à actualiser au fil des incidents.

## Build / CI

### Le job `test` échoue avec une erreur `better-sqlite3`
- **Cause** : tu as changé l'image de base CI vers Alpine. Pas de prebuild musl pour `better-sqlite3`.
- **Fix** : remettre `node:22-bookworm-slim` (Debian Slim glibc).

### `vue-tsc --noEmit` passe en local mais foire en CI
- **Cause** : la CI fait `vue-tsc --build` (compile full), pas `--noEmit` (type-check seul).
- **Fix** : tester avec `npm run build` (qui appelle `vue-tsc --build`) avant de push.

## Backend runtime

### `Boolean("false") === true` — env var qui ne fait pas ce qu'on attend
- **Symptôme** : `VISION_ENABLED=false` est interprété comme `true`.
- **Cause** : `z.coerce.boolean()` en JS — toute chaîne non vide est truthy.
- **Fix** : utiliser le helper `envBool()` défini en haut de `src/config.ts`. Comprend `true/false/1/0/yes/no/on/off`.

### `process.env.X` retourne undefined en prod
- **Cause** : la var n'a pas été ajoutée dans `unraid/boardgame-referee.xml`, donc l'admin Unraid ne l'a pas setée dans l'UI.
- **Fix** : règle des 3 fichiers — `src/config.ts` + `.env.example` + `unraid/boardgame-referee.xml`. Sinon la var est silencieusement absente.

### Une regex `\b` ne match pas un nom finissant par `é`
- **Cause** : `\b` sans flag `u` ne reconnaît que les boundaries `[A-Za-z0-9_]`.
- **Fix** : lookahead Unicode-aware avec flag `u` : `(?=\\s|$|[^\\p{L}\\p{N}_])`. Piège historiquement déclenché sur les mentions `@` Lorcana.

### Claude répond "je ne vois aucune image"
- **Cause** : `claude-ssh.ts` lance le CLI avec `--system-prompt` au lieu de `--append-system-prompt`. Le contrat d'utilisation des outils est écrasé.
- **Fix** : toujours `--append-system-prompt`.

### `ClaudeQuotaError` non détectée → "answer empty" générique
- **Cause** : le wording du message de quota a changé côté Anthropic.
- **Fix** : étendre `QUOTA_MARKERS` dans `claude-quota.ts`. Cf. `pipeline-rag/pause-quota-claude.md`.

## Retrieval / RAG

### Citation `[BASE p.4 : "..."]` ouvre la mauvaise page
- **Cause** : plusieurs livrets indexés partagent la page 4 (base + extension). La regex de citation ne capturait pas le nom du livret.
- **Fix** : cf. ARCHITECTURE.md § "Citations cliquables — format nom du livret". Le format est désormais `[<nom du livret>, p.X : "..."]` avec résolution `nameToId` côté frontend.

### Image PNG envoyée à Claude mais c'est la page 4 du mauvais livret
- **Cause** : groupage par `page` au lieu de `(livret, page)` dans `answer.ts`.
- **Fix** : cf. `pipeline-rag/vision-inline.md` — granularité composite obligatoire.

### Claude cite un livret en `[EXT p.4]` au lieu de `[Heavy Rain, p.4]`
- **Cause** : ancien format de citation pré-2026-04-11.
- **Fix** : c'est rétrocompatible côté frontend (regex avec fallback). Mais à terme migrer le SYSTEM_PROMPT pour imposer le nouveau format.

### Reranker retourne ~0 sur du contenu technique abstrait → enterre le bon chunk
- **Cause** : le reranker bge-v2-m3 est peu confiant sur du jargon technique.
- **Fix** : c'est précisément la motivation du blending position-aware (RAG Fusion v2). Cf. ADR-005. Si tu vois encore des cas, considérer ajuster les ratios (`75/25` → `85/15` sur top-3).

## Frontend

### `display: flex` dans une CSS scoped écrase le `hidden lg:flex` Tailwind
- **Cause** : la spécificité CSS scoped batt utilities.
- **Fix** : préférer Tailwind utilities partout. Si tu dois absolument scoped, utiliser `!important` ou `@apply`.

### Card autocomplete `@` ne matche pas un nom japonais / accentué
- **Cause** : `\b` regex Unicode-incompatible (cf. backend ci-dessus, même piège).
- **Fix** : `useArbiterMarkdown.ts:74-81` — lookahead `(?=\\s|$|[^\\p{L}\\p{N}_])` avec flag `u`.

### CardZoomModal ne s'ouvre pas après reload
- **Cause** : `cardLookup` n'a pas été reconstruit depuis les messages persistés en localStorage.
- **Fix** : `usePlaySession` appelle `rebuildFromMessages()` on mount. Vérifier que ce path n'a pas été cassé.

### SSE casse à 60s pile et UI affiche network error
- **Cause** : NPM `proxy_read_timeout 60s` + heartbeat backend pas émis.
- **Fix** : vérifier que `setInterval` heartbeat 8s est bien armé dans le service streamSSE. Pattern dans `routes/ask.ts`.

## Infra

### TEI répond `000` (curl no response)
- **Cause** : port custom Unraid (8099 au lieu de 80, 8990 au lieu de 8090).
- **Fix** : suspecter d'abord le port. Cf. mémoire `reference_unraid_ports.md`.

### Container `oracle` SSH timeout
- **Cause** : VM oracle down ou auth ed25519 cassée.
- **Fix** : 
  - `ssh -i /app/ssh/id_ed25519 oracle@<host> "cd /home/oracle/work && claude -p" <<< "test"`
  - Si timeout : SSH server down, restart la VM
  - Si `Permission denied` : vérifier `authorized_keys` côté VM, vérifier que la clé privée n'a pas été régénérée

### Wrapper oracle rejette toutes les commandes
- **Cause** : préfixe strict du wrapper modifié sans aligner les autres endroits.
- **Fix** : 3 endroits doivent matcher (`/home/oracle/bin/oracle-claude.sh` PREFIX, `CLAUDE_SSH_WORKDIR` env, `permissions.allow`/`additionalDirectories` du settings.json oracle). Cf. `securite/ssh-oracle.md`.

### Logs spam "EADDRINUSE :::3000"
- **Cause** : container restart trop rapide, l'ancien process pas encore mort.
- **Fix** : `docker compose down && docker compose up -d` (au lieu de restart).

## BDD

### Migration Drizzle qui ne s'applique pas
- **Cause** : `drizzle-kit generate` n'a pas détecté le diff (rare). Ou la table `__drizzle_migrations__` est désynchronisée.
- **Fix** : vérifier le contenu de `migrations/` vs `src/schema.ts`. Si nécessaire, `npm run db:migrate` manuel pour forcer.

### `database.db is locked`
- **Cause** : un autre process / container tient le fichier.
- **Fix** : `lsof /app/data/database.db` (depuis Unraid CLI) pour identifier. Souvent un ancien container qui n'est pas vraiment mort.