Starea de rulare, ce s-a verificat empiric in productie, cele 10 capcane gasite pe parcurs (cu #1 — /cleanup care omora sesiunile de lucru — marcata explicit), deciziile care nu se redeschid, si singurul lucru ramas: tokenul Proxmox cu ACL. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
199 lines
9.7 KiB
Markdown
199 lines
9.7 KiB
Markdown
# CONTEXT HANDOVER — Punte Discord → Claude Code (LXC 171)
|
|
|
|
**Data:** 2026-08-30 13:00 UTC · **Stare:** LIVRATA, in productie, pe `master` (impins)
|
|
**Commit curent:** `c8a5d41` · **Repo:** romfast/ROMFASTSQL · **Branch:** `master`
|
|
|
|
Nu e un task intrerupt. Puntea functioneaza si e folosita. Documentul asta exista ca o
|
|
sesiune noua sa poata prelua operarea si continuarea fara sa redescopere totul.
|
|
|
|
---
|
|
|
|
## 1. Ce e
|
|
|
|
Bot Discord care comanda LXC 171 (si, prin el, infrastructura) dintr-un canal privat, de pe
|
|
telefon. Bot subtire `discord.py` peste CLI-ul `claude`, cu **proces persistent per fir**
|
|
alimentat pe stdin cu `--input-format stream-json` — de aceea un mesaj trimis in timpul unui
|
|
tur functioneaza ca **steering**, nu deschide tur nou.
|
|
|
|
Plan sursa: `~/.gstack/projects/romfast-ROMFASTSQL/claude-master-plan-discord-bridge-20260830.md`
|
|
(15 taskuri, toate facute). Executat cu 5 agenti in 3 lane-uri paralele.
|
|
|
|
**Cod:** `proxmox/lxc171-claude-agent/discord-bridge/`
|
|
**Documentatie:** `discord-bridge/README.md` (operare completa), `discord-bridge/INTERFACES.md`
|
|
(contractul intre module — de citit inainte de orice modificare), `discord-bridge/security/README.md`.
|
|
|
|
---
|
|
|
|
## 2. Stare de rulare (verificata la 12:58 UTC)
|
|
|
|
| | |
|
|
|---|---|
|
|
| Serviciu | `systemctl --user status claude-discord` → **active**, `NRestarts=0` |
|
|
| Bot | `ClaudeAgent#7891`, app id `1543576449624186880` |
|
|
| Guild | Romfast `1430476698264145922` |
|
|
| Canal | **privat** `#claude-agent` `1543581063496859679` |
|
|
| User permis | `949388626146517022` (marius.mutu) |
|
|
| Director de lucru | `/workspace/claude-agent/` (repo git propriu) |
|
|
| Model | `sonnet` (default), `/model opus` per fir |
|
|
| Cost ziua curenta | $1.5456 (plafon `COST_CAP_USD_DAY=5.00`) |
|
|
| Comenzi slash | 7, inregistrate pe guild |
|
|
|
|
**Stare pe disc:** `~/.claude-discord/` — `env` (0600, token+allowlist), `state.json`,
|
|
`bot-settings.json` (hook), `approvals/`, `logs/`, `venv/`.
|
|
Cod versionat in repo; secretele NU.
|
|
|
|
### Comenzi (slash, nu prefix `!` — migrat)
|
|
`/new [fork]` · `/cd <cale>` · `/model <sonnet|opus>` · `/status` · `/stop` · `/cleanup [force]` · `/help`
|
|
|
|
Mesajele obisnuite (fara comanda) merg la Claude ca prompt.
|
|
|
|
---
|
|
|
|
## 3. Ce s-a verificat EMPIRIC in productie (nu doar teste)
|
|
|
|
Astea sunt afirmatii cu dovada, nu presupuneri:
|
|
|
|
| Ce | Dovada |
|
|
|---|---|
|
|
| Steering mid-tur | mesaj la 6s intr-un tool call de 25s a schimbat raspunsul final (ALFA→BETA), CLI 2.1.251 |
|
|
| Comenzi slash | 7 citite inapoi din API-ul Discord, nu doar din log |
|
|
| Confirmare operatiuni ireversibile | `12:24:25 PENDING` → butoane in canal → `12:24:39 ALLOW` → `/tmp/test-punte` sters |
|
|
| Fail-closed | acelasi `rm -rf` fara aprobare → `DENY` la timeout, directorul intact |
|
|
| Proces persistent | pid viu la 2 min dupa incheierea turului (reaper la 20 min) |
|
|
| `--resume` peste restart | aceeasi sesiune reluata dupa ce restartul a omorat procesul |
|
|
| `KillMode=control-group` | restart mid-sesiune, zero orfani |
|
|
| Parser tolerant | `rate_limit_event` aparut real in stream, ignorat cu un singur log |
|
|
| `/cleanup` dupa fix | rulare seaca pe container: „Niciun proces orfan" |
|
|
| Suita | **322 passed** cu discord.py, **319 passed + 3 skipped** fara |
|
|
|
|
Rulare teste: `cd proxmox/lxc171-claude-agent/discord-bridge && python3 -m pytest`
|
|
E2E (CLI real, ~40s, costa): `pytest -m e2e`
|
|
|
|
---
|
|
|
|
## 4. Capcane gasite pe parcurs — NU le reintroduce
|
|
|
|
Astea au costat timp si unele erau periculoase. Sunt fixate; sunt scrise aici ca sa nu
|
|
fie „simplificate" inapoi.
|
|
|
|
1. **`/cleanup` omora sesiunile de lucru.** Definitia initiala („orice proces `claude`
|
|
absent din `state.json`") prindea sesiunile Claude interactive de pe container:
|
|
rularea seaca propunea 25 de procese / 3864 MB, inclusiv sesiunea din care ar fi fost
|
|
data comanda. **Apartenenta la cgroup-ul `claude-discord.service` e conditie NECESARA
|
|
pentru toate familiile**, fail-closed la cgroup necitibil. Sesiunile de lucru stau in
|
|
`tmux-spawn-*.scope`. Exista test de regresie pe instantaneul real; verificat prin
|
|
mutant ca musca (fara filtru pica 3 teste).
|
|
|
|
2. **`pid_start_time` e in SECUNDE**, nu ticks (`session_store.py` converteste; `state.json`
|
|
viu contine secunde). Un consumator care compara ticks nu se potriveste niciodata — si
|
|
cum acea comparatie protejeaza turul in desfasurare, esecul e tacut si face eligibil
|
|
pentru omorare exact ce trebuia protejat. Documentat in `INTERFACES.md`.
|
|
|
|
3. **`/proc/uptime` e virtualizat de lxcfs, `starttime` nu.** Calculul naiv al varstei dadea
|
|
`4123168064` pentru un proces de 10 minute. Se foloseste `btime` din `/proc/stat`.
|
|
|
|
4. **Utilizatorul containerului se numeste `claude`**, deci potrivirea pe subsirul `/claude`
|
|
marca orice proces din home-ul lui. Potrivire pe basename per argument.
|
|
|
|
5. **Log dublat:** unitul redirecteaza stdout in `bot.log` (`StandardOutput=append:`) SI
|
|
codul avea `FileHandler` pe acelasi fisier. `FileHandler` ramane doar la rulare manuala
|
|
(`INVOCATION_ID` absent).
|
|
|
|
6. **`View Channel` lipsea** din permisiunile de invitatie — fara ea botul nu vede canalul,
|
|
oricat de permis ar fi in allowlist. Link corect (`permissions=309237763136`):
|
|
`https://discord.com/oauth2/authorize?client_id=1543576449624186880&scope=bot%20applications.commands&permissions=309237763136`
|
|
|
|
7. **Comenzile slash au nevoie de scope `applications.commands`** (nu doar `bot`) si de
|
|
**sync pe guild** (instantaneu; global ~1h). Fiecare comanda face `defer()` — altfel
|
|
Discord marcheaza interactiunea esuata dupa 3s desi comanda a rulat.
|
|
|
|
8. **`~/bin` trebuie sa fie in PATH-ul unitului**, altfel wrapperul `infra` e prezent pe
|
|
disc dar negasibil de bot (stratul 4 de securitate inert).
|
|
|
|
9. **Raportul `/cleanup`** depasea 2000 de caractere cu rezultatele atasate → mesaj respins
|
|
de Discord exact la `force:True`. Buget de caractere `MAX_REPORT_CHARS=1800`.
|
|
|
|
10. **Discord API respinge User-Agent-ul implicit al `urllib`** cu `error code: 1010`
|
|
(Cloudflare). Arata identic cu un refuz de permisiuni. Pune un User-Agent real.
|
|
|
|
---
|
|
|
|
## 5. Decizii de arhitectura — luate deliberat, NU le redeschide
|
|
|
|
Sunt in plan si au fost reconfirmate de utilizator. Un agent nou care le „descopera" ca
|
|
probleme pierde timp si risca sa strice lucruri.
|
|
|
|
- **`--permission-mode bypassPermissions` e intentionat.** Regulile `deny` sunt strat
|
|
cosmetic, NU bariera — verificat: `/usr/bin/ssh -V` si `bash -c "ssh -V"` trec pe langa.
|
|
Bariera reala e hook-ul PreToolUse.
|
|
- **Accesul larg la infrastructura si la `/workspace` e FUNCTIONALITATE ceruta**, nu bug.
|
|
De aceea: fara user separat `cdbot`, fara allowlist de proiecte pentru `/cd`.
|
|
- **Fara audit append-only** (stratul 3), respins constient. **Consecinta asumata:** la o
|
|
problema nu exista jurnal independent; logurile pot fi sterse de chiar procesul care le
|
|
scrie. Hook-ul si botul ruleaza sub acelasi user, deci un agent isi poate scrie singur
|
|
`"status":"allow"`.
|
|
- **Fara reluare automata a turului pierdut** — risc de dubla executie sub bypassPermissions.
|
|
- Fara Agent SDK, fara dashboard web, fara user separat, fara test golden pe JSONL inregistrat.
|
|
|
|
---
|
|
|
|
## 6. CE A RAMAS DE FACUT
|
|
|
|
### 6.1 Token Proxmox cu ACL (singurul lucru neterminat)
|
|
|
|
**Nu e conditie de functionare.** Puntea merge acum folosind cheile SSH root deja prezente
|
|
pe container — adica orice tur are drepturi depline pe cluster. Tokenul restrange asta.
|
|
|
|
Comenzile exacte sunt in `discord-bridge/security/README.md`. Pe scurt, ca root pe `pvemini`:
|
|
|
|
```
|
|
pveum user add claude-bridge@pve
|
|
pveum role add ClaudeBridge -privs "Datastore.Audit,Sys.Audit,Sys.Console,Sys.Syslog,VM.Audit,VM.Monitor,VM.Console,VM.PowerMgmt"
|
|
pveum acl modify /
|
|
pveum user token add claude-bridge@pve discord --privsep 1
|
|
pveum acl modify / --tokens 'claude-bridge@pve!discord'
|
|
```
|
|
|
|
`VM.Allocate` (creare/distrugere guest) e lasat afara INTENTIONAT.
|
|
|
|
Apoi: secretul (afisat o singura data) in `~/.claude-discord/env` ca `PVE_TOKEN_ID` /
|
|
`PVE_TOKEN_SECRET`, `chmod 600`. De decis daca ACL ramane pe `/` sau se restrange la un
|
|
subset de guest-uri.
|
|
|
|
### 6.2 Idei ramase, neprioritizate
|
|
- Atasamente Discord → Claude (download in /tmp + cale in prompt) — era v1.1 in plan.
|
|
- Rate limit-ul Discord la render e singura degradare tacuta ramasa: are tratare (interval
|
|
adaptiv 1s→5s), nu are test. Acceptat, fiindca esecul e intarziere, nu pierdere.
|
|
- `/cleanup` nu prinde procese iesite complet din cgroup — pret deliberat, ca sa nu atinga
|
|
niciodata sesiunile interactive.
|
|
|
|
---
|
|
|
|
## 7. Fisiere cheie
|
|
|
|
| Fisier | De ce conteaza |
|
|
|---|---|
|
|
| `discord-bridge/INTERFACES.md` | contractul intre module + proprietatea pe fisiere. **De citit primul.** |
|
|
| `discord-bridge/README.md` | operare, instalare, depanare, limitari oneste |
|
|
| `discord-bridge/runner.py` | proces persistent, reaper, respawn `--resume` |
|
|
| `discord-bridge/session_store.py` | state.json atomic, lock per fir, PID reuse |
|
|
| `discord-bridge/cleanup.py` | vezi capcana #1 — cea mai periculoasa zona |
|
|
| `discord-bridge/security/confirm_hook.py` | hook PreToolUse, fail-closed |
|
|
| `discord-bridge/security/infra` | wrapper cu lista explicita de hosturi (exit 3 la host necunoscut) |
|
|
| `discord-bridge/ops/claude-discord.service` | `KillMode=control-group`, MemoryHigh 3G / Max 6G, PATH |
|
|
| `~/.gstack/.../claude-master-plan-discord-bridge-20260830.md` | planul original, cu justificarile |
|
|
|
|
## 8. Comenzi de operare
|
|
|
|
```bash
|
|
systemctl --user status claude-discord
|
|
journalctl --user -u claude-discord -n 200
|
|
tail -f ~/.claude-discord/logs/bot.log
|
|
tail -f ~/.claude-discord/logs/confirm_hook.log # cine a cerut/primit aprobare
|
|
cd proxmox/lxc171-claude-agent/discord-bridge && python3 -m pytest
|
|
python3 cleanup.py # rulare seaca, sigura
|
|
```
|
|
|
|
**Memorie:** un fir costa ~440 MB (claude 300 + servere MCP 137). Plafon 4 procese → ~1,8 GB,
|
|
sub `MemoryHigh=3G`. Containerul are 16 GB.
|