Indice
Claude Haiku (veloce, economico) classifica ogni segnalazione in tempo reale
e genera una risposta comprensibile all'utente.
Claude Code CLI (Sonnet/Opus) entra nel codebase, modifica i file,
fa deploy e verifica — in piena autonomia.
Password gate per la chiusura manuale dei ticket da parte degli utenti. Isolamento per tenant_id su ogni query. Lock file per prevenire esecuzioni parallele del worker. Budget cap per limitare i costi AI per ticket.
| Fase | Azione | Esito se fallisce |
|---|---|---|
| #1 Auth | Login admin via /auth/login → JWT | Exit 1, log ERRORE CRITICO |
| #2 Fetch | GET /feedback?status=in_lavorazione | Exit 0, nessuna azione |
| #3 Prompt | Costruisce prompt contestuale, salva su /tmp/taxai-prompt-{id}.txt | — |
| #4 Claude | docker exec -u developer devenv → claude --dangerously-skip-permissions | Timeout → rimette aperto + nota |
| #5 Deploy | Esegue /tmp/deploy-{id}.sh (creato da Claude) → docker cp | Log warning, prosegue |
| #6 Resolve | POST /feedback/{id}/resolve con resolution_password | Aggiunge nota manuale |
| #7 Broadcast | INSERT notifications per tutti gli utenti attivi del tenant | Log silenzioso (non blocca) |
feedback_reports — da creare nel DB di notifiche del microservizioCREATE TABLE feedback_reports (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id INT UNSIGNED NOT NULL,
-- Mittente
user_id INT UNSIGNED,
user_email VARCHAR(255),
user_role VARCHAR(50),
page_url VARCHAR(1000), -- URL/hash della pagina al momento della segnalazione
-- Classificazione utente
tipo ENUM('bug','ux','funzionalita','domanda','altro') NOT NULL DEFAULT 'bug',
priorita ENUM('alta','media','bassa') NOT NULL DEFAULT 'media',
descrizione TEXT NOT NULL,
attachment LONGTEXT, -- base64 immagine o NULL
-- Stato workflow
status ENUM('aperto','in_lavorazione','risolto','chiuso')
NOT NULL DEFAULT 'aperto',
nota_admin TEXT, -- nota interna dell'admin
-- Output AI (Claude Haiku)
ai_categoria VARCHAR(100), -- es: "Calcolo IMU", "Login", "Dashboard"
ai_priorita ENUM('alta','media','bassa'),
ai_suggerimento TEXT, -- analisi tecnica per admin
ai_risposta TEXT, -- risposta comprensibile per l'utente
ai_processed TINYINT(1) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_tenant_status (tenant_id, status),
INDEX idx_tenant_user (tenant_id, user_id),
INDEX idx_ai_priorita (tenant_id, ai_priorita),
INDEX idx_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
tenant_id. In sistemi non multi-tenant
si può fissare tenant_id = 1 per tutta l'applicazione.
Valida tipo e priorità, inserisce il record, chiama classifyWithAI() in modo sincrono ma con timeout 10s (non blocca la response).
Chiama Claude Haiku via REST API Anthropic. Estrae JSON: categoria, priorità, suggerimento tecnico, risposta utente. Fallback se API key assente.
Aggiorna status/nota. Se lo status diventa 'risolto' dalla prima volta, chiama automaticamente broadcastResolution().
Password gate per chiusura manuale lato utente. Confronta con RESOLUTION_PASSWORD env. Se ok → update + broadcast.
Legge tutti gli utenti attivi (escluso public) dal DB auth. INSERT massivo nella tabella notifications. Try/catch silenzioso.
KPI: totale, aperti, in_lavorazione, risolti, alta_priorità, da_analizzare. Query unica con SUM condizionali.
private function classifyWithAI(int $id, string $descrizione, string $tipo, string $prioritaUtente): void
{
if (!$this->claudeApiKey) {
// Fallback senza AI: aggiorna con dati base
$this->pdo->prepare("UPDATE feedback_reports SET
ai_categoria = :cat, ai_priorita = :pri,
ai_suggerimento = :sug, ai_risposta = :risp, ai_processed = 1
WHERE id = :id")->execute([
':cat' => ucfirst($tipo),
':pri' => $prioritaUtente,
':sug' => 'Analisi AI non disponibile. Revisionare manualmente.',
':risp' => 'Grazie per la segnalazione. Il team la verificherà al più presto.',
':id' => $id,
]);
return;
}
$prompt = <<<PROMPT
Sei un assistente tecnico per {APP_NAME}.
Analizza questa segnalazione e rispondi SOLO con JSON valido:
{
"categoria": "stringa breve (es: Login, Dashboard, PDF, Pagamenti)",
"priorita": "alta|media|bassa",
"suggerimento": "analisi tecnica per admin (max 3 frasi)",
"risposta_utente": "risposta rassicurante all'utente in linguaggio semplice (max 3 frasi)"
}
Tipo dichiarato: {$tipo}
Segnalazione: "{$descrizione}"
PROMPT;
$payload = [
'model' => 'claude-haiku-4-5-20251001', // veloce ed economico
'max_tokens' => 300,
'messages' => [['role' => 'user', 'content' => $prompt]],
];
$ch = curl_init('https://api.anthropic.com/v1/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10, // non bloccare la response più di 10s
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . $this->claudeApiKey,
'anthropic-version: 2023-06-01',
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Parse e salva
if ($httpCode === 200 && $response) {
$decoded = json_decode($response, true);
$text = $decoded['content'][0]['text'] ?? '';
if (preg_match('/\{[\s\S]*\}/', $text, $m)) {
$ai = json_decode($m[0], true);
// aggiorna DB con dati AI ...
}
}
}
| Metodo | Path | Auth richiesta | Descrizione |
|---|---|---|---|
| POST | /feedback |
API-Key + JWT (tutti i ruoli) | Crea nuova segnalazione. Risponde con dati + risposta AI in tempo reale. |
| GET | /feedback/mine |
API-Key + JWT (tutti i ruoli) | Le ultime 20 segnalazioni dell'utente autenticato. Per il tab "Le mie". |
| GET | /feedback/stats |
API-Key + JWT (admin only) | KPI: totale, aperti, in lavorazione, risolti, alta priorità, da analizzare. |
| GET | /feedback |
API-Key + JWT (admin only) | Lista segnalazioni con filtri: ?status=aperto&tipo=bug&ai_priorita=alta |
| GET | /feedback/{id} |
API-Key + JWT (admin o autore) | Singola segnalazione con tutti i campi AI. |
| PUT | /feedback/{id} |
API-Key + JWT (admin only) | Aggiorna status, nota_admin, priorita. Trigger broadcast se status → risolto. |
| POST | /feedback/{id}/resolve |
API-Key + JWT + resolution_password | Chiusura con password gate. Usato dal worker e dal modal utente (fase 2). |
// Request
POST /feedback
X-API-Key: your-api-key
Authorization: Bearer <JWT>
Content-Type: application/json
{
"tipo": "bug", // bug | ux | funzionalita | domanda | altro
"priorita": "alta", // alta | media | bassa
"descrizione": "Il calcolo IMU restituisce un importo errato per i terreni agricoli",
"page_url": "https://app.esempio.it/#view-imu",
"attachment": "data:image/png;base64,..." // opzionale
}
// Response (con risposta AI già pronta)
{
"success": true,
"data": {
"id": 42,
"tipo": "bug",
"status": "aperto",
"ai_categoria": "Calcolo IMU",
"ai_priorita": "alta",
"ai_risposta": "Abbiamo ricevuto la tua segnalazione riguardo al calcolo IMU per terreni agricoli.
Il team tecnico la verificherà nelle prossime ore e provvederà a correggerla.
Nel frattempo puoi usare il calcolo manuale come riferimento.",
"ai_processed": 1,
"created_at": "2026-03-10 14:23:11"
}
}
define('LOCK_FILE', '/tmp/app-segnalazioni-worker.lock');
define('LOG_FILE', '/var/log/app/segnalazioni-worker.log');
define('API_KEY', 'your-api-key');
define('RESOLUTION_PW', 'your-resolution-password'); // env: RESOLUTION_PASSWORD
define('AUTH_API', 'http://localhost:4111'); // porta auth microservice
define('NOTIF_API', 'http://localhost:4108'); // porta notification microservice
define('CLAUDE_BIN', '/usr/bin/claude'); // path al binario Claude Code
define('PROJECT_DIR', '/projects/your-app'); // path nel container devenv
define('PROJECT_DIR_HOST','/var/www/your-app'); // path sull'host (bind mount)
define('MAX_BUDGET_USD', '2.00'); // limite costo AI per segnalazione
define('CLAUDE_TIMEOUT', 1500); // 25 min (cron ogni 30 min)
// 1. Costruisci il prompt contestuale e salvalo su file condiviso
$prompt = buildPrompt($report); // vedi sezione "Prompt Engineering"
$promptFile = PROJECT_DIR_HOST . '/tmp/taxai-prompt-' . $id . '.txt';
file_put_contents($promptFile, $prompt);
// 2. Invoca Claude Code nel container devenv come utente non-root
// --dangerously-skip-permissions: evita conferme interattive (obbligatorio per autonomia)
// --output-format stream-json: output incrementale, catturabile anche con timeout
// --max-turns 30: previene loop infiniti di tool-use
$containerPromptFile = PROJECT_DIR . '/tmp/taxai-prompt-' . $id . '.txt';
$innerCmd = sprintf(
'cd %s && CLAUDE_PROMPT=$(cat %s) && timeout %d %s --dangerously-skip-permissions --output-format stream-json --max-turns 30 -p "$CLAUDE_PROMPT"',
escapeshellarg(PROJECT_DIR),
escapeshellarg($containerPromptFile),
CLAUDE_TIMEOUT,
CLAUDE_BIN
);
$cmd = sprintf(
'docker exec -u developer your-devenv-container bash -c %s 2>&1',
escapeshellarg($innerCmd)
);
exec($cmd, $output, $exitCode);
// 3. Parsa l'output stream-JSON per estrarre il testo
$result = parseStreamJson($output);
// 4. Se Claude ha creato un deploy script, eseguilo sull'host
$deployScript = PROJECT_DIR_HOST . '/tmp/deploy-' . $id . '.sh';
if (file_exists($deployScript)) {
exec('bash ' . escapeshellarg($deployScript));
unlink($deployScript);
}
// 5. Chiudi il ticket se Claude ha completato
if ($exitCode === 0) {
resolveReport($id, $token); // POST /feedback/{id}/resolve
}
Il prompt deve fornire tutto il contesto necessario e istruzioni precise. Claude non può chiedere chiarimenti in modalità autonoma.
Sei Claude Code in modalità AUTONOMA nel progetto {APP_NAME}.
Working directory: {PROJECT_DIR_CONTAINER}
Ambiente: container {DEVENV_CONTAINER} su rete Docker {DOCKER_NETWORK}
IMPORTANTE: NON usare SSH o docker — non disponibili nel container.
SEGNALAZIONE DA FIXARE:
ID: {id}
Tipo: {tipo}
Priorità: {priorita}
Descrizione: {descrizione}
Vista/URL: {view} ({page_url})
Suggerimento AI precedente: {ai_suggerimento}
ISTRUZIONI (esegui in sequenza, senza chiedere conferma):
1. Leggi docs/CONTEXT_LAST_SESSION.md per capire lo stato del progetto.
2. Analizza il bug con Grep e Read per trovare il codice rilevante.
3. Identifica la root cause. Sii preciso.
4. Applica il fix con Edit. Modifica solo ciò che serve.
5. Deploy:
- File JS/HTML: bind mount, NESSUN deploy necessario.
- File PHP: scrivi deploy script in /tmp/deploy-{id}.sh (il worker lo eseguirà).
6. Verifica con curl interno verso i microservizi.
7. Chiudi il ticket via API (OBBLIGATORIO se fix riuscito):
TOKEN=$(curl -s -X POST http://{auth-ms}/auth/login ... | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
curl -s -X POST http://{notif-ms}/feedback/{id}/resolve -H "Authorization: Bearer $TOKEN" \
-d '{"resolution_password":"{RESOLUTION_PW}"}'
8. Se NON riesci, aggiungi nota e lascia aperto (non inventare soluzioni).
Procedi in autonomia. Non chiedere conferme. Sii efficiente.
# Processa segnalazioni ogni 30 minuti (worker ha timeout 25min + lock per sicurezza)
*/30 * * * * root /usr/bin/php8.4 /var/www/your-app/scripts/segnalazioni-worker.php >> /var/log/app/cron.log 2>&1
/**
* Estrae testo dall'output --output-format stream-json di Claude Code CLI.
* Ogni riga è un oggetto JSON con type: result | assistant | tool_use | ...
* Se trova type=result usa il campo result finale; altrimenti concatena i testi parziali.
*/
function parseStreamJson(array $lines): string {
$texts = [];
foreach ($lines as $line) {
$line = trim($line);
if ($line === '') continue;
$obj = json_decode($line, true);
if (!is_array($obj)) continue;
if ($obj['type'] === 'result' && isset($obj['result'])) {
return (string) $obj['result']; // riga finale: usa questa
}
if ($obj['type'] === 'assistant' && isset($obj['message']['content'])) {
foreach ($obj['message']['content'] as $block) {
if (($block['type'] ?? '') === 'text' && isset($block['text'])) {
$texts[] = $block['text'];
}
}
}
}
return implode("\n\n", $texts); // fallback: concatena testi parziali
}
- ✓ FAB arancione fisso bottom-right (visibile solo utenti autenticati)
- ✓ Tab "Nuova segnalazione" + tab "Le mie"
- ✓ Campo tipo (bug/ux/funzionalità/domanda/altro)
- ✓ Campo priorità (alta/media/bassa)
- ✓ Textarea descrizione + drag&drop screenshot
- ✓ Cattura automatica URL/hash corrente
- ✓ Invia → mostra risposta AI (Fase 2)
- ✓ Mostra
ai_rispostada Claude Haiku - ✓ Pulsanti "Sì, risolto" / "No, problema persiste"
- ✓ "Sì" → password gate → POST /feedback/{id}/resolve
- ✓ "No" → conferma ricezione, ticket rimane aperto
- ✓ Tab "Le mie": lista con status badge
- ✓ Pulsante "Segna all'admin" / "Risolvi" per ogni ticket
<!-- Floating Action Button Segnalazioni -->
<button id="feedback-fab" title="Segnala un problema"
style="position:fixed;bottom:1.5rem;right:1.5rem;z-index:9000;
width:3.2rem;height:3.2rem;border-radius:50%;border:none;
background:#f97316;color:white;font-size:1.3rem;cursor:pointer;
box-shadow:0 4px 16px rgba(0,0,0,0.25);display:none">
<i class="fas fa-exclamation-circle"></i>
</button>
<!-- Modal overlay -->
<div id="feedback-modal-overlay"
style="display:none;position:fixed;inset:0;z-index:9100;
background:rgba(0,0,0,0.5);align-items:center;justify-content:center">
<div style="background:white;border-radius:1rem;width:min(520px,95vw);
max-height:85vh;overflow-y:auto;padding:1.5rem;position:relative">
<!-- Tab bar -->
<div style="display:flex;gap:0.5rem;margin-bottom:1rem">
<button id="feedback-tab-nuova" class="tab-btn tab-active">Nuova segnalazione</button>
<button id="feedback-tab-mie" class="tab-btn">Le mie</button>
</div>
<!-- Fase 1: form -->
<div id="feedback-panel-nuova">
<div id="feedback-phase1">
<p id="feedback-page-label" style="font-size:0.8rem;color:#64748b;margin-bottom:0.8rem"></p>
<select id="feedback-tipo" style="width:100%;margin-bottom:0.6rem;padding:0.5rem">
<option value="bug">🐛 Bug / Errore</option>
<option value="ux">🎨 Problema UX</option>
<option value="funzionalita">✨ Richiesta funzionalità</option>
<option value="domanda">❓ Domanda</option>
<option value="altro">📋 Altro</option>
</select>
<select id="feedback-priorita" style="width:100%;margin-bottom:0.6rem;padding:0.5rem">
<option value="media">Priorità media</option>
<option value="alta">🔴 Priorità alta</option>
<option value="bassa">Priorità bassa</option>
</select>
<textarea id="feedback-descrizione" rows="4" placeholder="Descrivi il problema..."
style="width:100%;padding:0.6rem;margin-bottom:0.8rem;resize:vertical"></textarea>
<!-- drag&drop screenshot -->
<div id="feedback-drop-zone" style="border:2px dashed #cbd5e1;border-radius:0.5rem;
padding:1rem;text-align:center;cursor:pointer;font-size:0.85rem;color:#64748b">
📎 Trascina uno screenshot qui oppure <u>clicca per allegare</u>
</div>
<input id="feedback-file-input" type="file" accept="image/*" style="display:none">
<div id="feedback-attachment-preview" style="display:none;margin-top:0.5rem">
<img id="feedback-attachment-img" style="max-width:100%;border-radius:0.4rem">
<button id="feedback-attachment-remove">✕ Rimuovi</button>
</div>
<p id="feedback-error-msg" style="color:#dc2626;font-size:0.85rem;margin-top:0.5rem"></p>
<button id="feedback-submit-btn" style="width:100%;margin-top:0.8rem;padding:0.7rem;
background:#f97316;color:white;border:none;border-radius:0.5rem;cursor:pointer;font-weight:600">
<i class="fas fa-paper-plane"></i> Invia segnalazione
</button>
</div>
<!-- Fase 2: risposta AI -->
<div id="feedback-phase2" style="display:none">
<h3 style="margin-bottom:0.8rem">🤖 Risposta del sistema</h3>
<div id="feedback-ai-risposta" style="background:#f0f9ff;border-radius:0.5rem;padding:1rem;
border-left:4px solid #0ea5e9;font-size:0.9rem"></div>
<p style="margin-top:1rem;font-weight:600">Questo ha risposto alla tua domanda?</p>
<div style="display:flex;gap:0.6rem;margin-top:0.6rem">
<button id="feedback-yes-btn">✅ Sì, risolto</button>
<button id="feedback-no-btn">❌ No, problema persiste</button>
</div>
<!-- Password gate per chiusura manuale -->
<div id="feedback-password-gate" style="display:none;margin-top:1rem">
<input id="feedback-resolve-password" type="password" placeholder="Password di conferma">
<button id="feedback-confirm-resolve-btn">Conferma risoluzione</button>
<p id="feedback-password-error" style="color:#dc2626;font-size:0.8rem"></p>
</div>
<div id="feedback-phase2-success" style="display:none;color:#16a34a;margin-top:0.8rem">
✅ Ottimo! Il ticket è stato chiuso. Grazie per il feedback.
</div>
</div>
</div>
<!-- Tab: Le mie segnalazioni -->
<div id="feedback-panel-mie" style="display:none">
<div id="feedback-mie-loading">Caricamento...</div>
<div id="feedback-mie-empty" style="display:none">Nessuna segnalazione inviata.</div>
<div id="feedback-mie-list"></div>
</div>
</div>
</div>
submitBtn.addEventListener('click', async function() {
var descrizione = descEl.value.trim();
if (descrizione.length < 10) {
errorMsg.textContent = 'Descrivi il problema in almeno 10 caratteri.';
return;
}
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Invio in corso...';
var payload = {
tipo: document.getElementById('feedback-tipo').value,
priorita: document.getElementById('feedback-priorita').value,
descrizione: descrizione,
page_url: window.location.href, // cattura URL corrente
attachment: currentAttachmentB64 || null, // base64 screenshot
};
try {
var res = await AppAPI.submitFeedback(payload); // POST /feedback
var aiRisposta = res.data?.ai_risposta || 'Segnalazione ricevuta.';
var reportId = res.data?.id;
// Mostra Fase 2 con risposta AI
phase1.style.display = 'none';
phase2.style.display = 'block';
aiRispEl.textContent = aiRisposta;
// Fase 2: gestione "Sì risolto" con password gate
yesBtn.onclick = function() {
pwGate.style.display = 'block';
};
pwConfBtn.onclick = async function() {
var pw = pwInput.value.trim();
if (!pw) return;
var r = await AppAPI.resolveFeedback(reportId, pw); // POST /feedback/{id}/resolve
if (r.success) {
p2success.style.display = 'block';
pwGate.style.display = 'none';
} else {
pwErrEl.textContent = r.error || 'Password non corretta.';
}
};
} catch(err) {
errorMsg.textContent = 'Errore: ' + (err.message || 'Impossibile inviare.');
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-paper-plane"></i> Invia segnalazione';
}
});
- Crea la tabella
feedback_reportsnel DB del microservizio notifiche (schema §3). - Copia
FeedbackService.phpinservices/notification-ms/src/Services/. Adatta il namespace e la stringa DSN del DB auth. - Registra gli endpoint in
services/notification-ms/public/index.php(vedi §5). Assicurati che il middleware JWT sia già attivo nel servizio. - Aggiungi le variabili d'ambiente:
ANTHROPIC_API_KEY=sk-ant-... # per Claude Haiku RESOLUTION_PASSWORD=your-secret # password gate chiusura manuale - Aggiungi nginx route:
Poi:location /api/feedback { proxy_pass http://notification_ms; }docker restart your-gateway - Aggiungi HTML (FAB + modal overlay) in
app/index.html(vedi §7). - Aggiungi JS: funzione
initFeedbackFab()chiamata dopo login inapp.js. ImplementaAppAPI.submitFeedback(),AppAPI.resolveFeedback(),AppAPI.getMyFeedback(). - Copia il worker
scripts/segnalazioni-worker.phpnel progetto. Adatta le costanti (api key, password, porte, container name, project dir). - Installa il crontab sull'host:
echo "*/30 * * * * root php8.4 /var/www/your-app/scripts/segnalazioni-worker.php" \ > /etc/cron.d/your-app-segnalazioni - Assicurati che il container devenv abbia accesso alla rete Docker del progetto:
docker network connect your-app-network your-devenv-container - Aggiungi la view admin
#view-segnalazionicon KPI, filtri, tabella segnalazioni e pulsanti azioni (in_lavorazione / risolto / chiudi). - Testa: apri l'app, clicca il FAB, invia una segnalazione di test. Verifica che la risposta AI arrivi. Imposta status "in_lavorazione" dall'admin. Attendi il prossimo cron (o esegui il worker manualmente) e verifica il fix.
| Variabile | Dove | Valore | Note |
|---|---|---|---|
ANTHROPIC_API_KEY |
.env / docker-compose | sk-ant-api03-... |
Per Claude Haiku (classificazione). Se assente → fallback senza AI. |
RESOLUTION_PASSWORD |
.env / docker-compose | Stringa segreta | Password gate per chiusura manuale ticket. Uguale in FeedbackService e worker. |
CLAUDE_BIN |
Worker PHP (define) | /usr/bin/claude |
Path al binario Claude Code CLI nel container devenv. |
PROJECT_DIR |
Worker PHP (define) | /projects/your-app |
Path del progetto NEL container devenv. |
PROJECT_DIR_HOST |
Worker PHP (define) | /var/www/your-app |
Path del progetto SULL'HOST (bind mount condiviso). |
CLAUDE_TIMEOUT |
Worker PHP (define) | 1500 (25 min) |
Timeout in secondi per ogni fix Claude. Deve essere < intervallo cron. |
MAX_BUDGET_USD |
Worker PHP (define) | 2.00 |
Budget massimo per segnalazione. Non ancora enforced da Claude Code CLI — da implementare. |
DEVENV_CONTAINER |
Worker PHP (docker exec) | your-devenv-container |
Nome del container devenv dove gira Claude Code CLI. |
DEVENV_USER |
Worker PHP (docker exec -u) | developer |
User non-root nel container. Root bloccato da --dangerously-skip-permissions. |
Il campo RESOLUTION_PASSWORD deve essere in variabile d'ambiente,
mai hardcoded nel codice committato. Usare una stringa lunga e casuale.
Il gate esiste per prevenire chiusure accidentali da parte degli utenti.
Il flag --dangerously-skip-permissions è necessario per l'autonomia.
Limitare il suo uso al solo container devenv, mai sull'host direttamente.
Assicurarsi che il container devenv non abbia accesso a dati sensibili di produzione.
Ogni query API deve filtrare per tenant_id estratto dal JWT.
Un utente non deve poter vedere o modificare segnalazioni di altri tenant.
Verificare nella route PUT e GET /{id} che il tenant_id combaci.
Il worker usa un lock file (/tmp/app-worker.lock) per prevenire
esecuzioni parallele. Verificare periodicamente che il lock non rimanga "zombie"
se il server viene riavviato senza pulizia del /tmp.
Gli screenshot vengono salvati come base64 in LONGTEXT.
Per volumi alti, considerare di salvarli su S3/MinIO e memorizzare solo l'URL.
Limitare la dimensione lato frontend prima dell'encoding (max 1-2MB consigliato).
La chiamata Claude Haiku è sincrona nella request dell'utente (timeout 10s). Per produzione ad alto traffico, pubblicare un messaggio su RabbitMQ/Redis e processare in background. La response all'utente includerà dati base, con AI arrivata in un secondo momento via polling.
--output-format stream-json, Claude produce output solo al completamento di ogni tool call.
Con timeout aggressivi (es. 124 = SIGTERM), l'output potrebbe essere vuoto anche se Claude ha fatto progressi.
Il parser parseStreamJson() gestisce questo caso usando i testi parziali come fallback.
Impostare CLAUDE_TIMEOUT a un valore ragionevole (almeno 10 minuti per bug semplici).