Incident: W4 File Upload Broken — 2026-07-01
| Pole | Wartość |
|---|---|
| ID | INCIDENT-2026-07-01-002 |
| Platforma | W4 (w4.pinbox24.com) |
| Severność | P1 — upload plików niedostępny |
| Status | RESOLVED |
| Wykryto | 2026-07-01T12:37Z |
| Root cause ustalony | 2026-07-01T12:50Z |
| Hotfix wdrożony | 2026-07-01T13:05Z (s3-v2-v42-prod PM2 reload) |
| Zweryfikowano | 2026-07-01T16:00Z (patch na linii 162, v42-prod stable 0 restartów) |
| GH Issue | radieu/p24-infra#2403 |
Symptom
Upload pliku w Pinbox24 W4 (w4.pinbox24.com) zwraca HTTP 500. Przeglądarka następnie robi GET na ten sam URL i dostaje 404.
{"success":false,"message":"Method not found.","stack":"HTTP404Error: Method not found.\n at Object.exports.notFoundError (/app/src/util/ErrorHandler.ts:6:11)..."}Oś czasu
| Czas (UTC) | Zdarzenie |
|---|---|
| 12:37 | Użytkownik testuje upload W4 po naprawie W3 |
| 12:38 | POST /api/offices/files/upload → HTTP 500 (nginx) |
| 12:38 | Browser fallback: GET /api/offices/files/upload → 404 |
| 12:50 | Root cause ustalony z logów v42-prod i kodu s3-v2-v42-prod |
| 13:05 | Hotfix wdrożony: res.json({ result: filesAdded }) + PM2 reload 4 workers |
| 15:14 | Rotation agent zrestartował s3-v2-v42-prod (Wasabi key rotation) — patch zgubiony |
| 15:32 | Patch re-aplikowany ponownie (Python script via SSH) |
| 15:45 | v42-prod crash-loop (506 restartów) — root cause: 8 brakujących pinbox24Public* zmiennych |
| 15:58 | Druga sesja naprawiła crash-loop v42-prod (dodała brakujące env vars) |
| 16:00 | Weryfikacja: patch na miejscu (linia 162), v42-prod 2 workers online, 0 restartów |
Root cause
Contract mismatch między s3-v2-v42-prod a v42-prod.
Przepływ W4
Browser → api.w4.pinbox24.com → nginx → v42-prod (TypeScript)
v42-prod uploadFile controller → POST http://s3-v2-v42-prod:3000/api/v3/storage/{officeId}/upload
s3-v2-v42-prod → Wasabi + MongoDB → response
Błąd
s3-v2-v42-prod storage.controller.js zwraca:
res.json(filesAdded); // bare array [fileDoc, ...]v42-prod s3Request.helper.js sprawdza:
if (httpResponse.statusCode === 200 && body && body.result && body.result[0]) {
resolve(body.result[0]); // ← body.result jest undefined na bare array
} else {
reject(new Error(`...S3_statusCode: ${httpResponse.statusCode}...`));
}body.result jest undefined na bare array → warunek false → reject → v42-prod zwraca 500.
Potwierdzenie z logów
v42-prod err log 14:38 (CEST = 12:38 UTC):
Server: Error: Error occurred while uploading the file.
Reason: S3_statusCode: 200 and S3_statusMessage: OK
S3_statusCode: 200 dowodzi, że Wasabi upload i MongoDB save w s3-v2-v42-prod działają poprawnie.
Fix
Jedna linia w s3-v2-v42-prod /app/dist/apps/storage/storage.controller.js:
// PRZED (linia ~65):
res.json(filesAdded);
// PO:
res.json({ result: filesAdded });Następnie: docker exec s3-v2-v42-prod pm2 restart s3-v2-v42-prod_backend
Hotfix jest idempotentny — files w Wasabi i MongoDB są poprawne, tylko response wraca do przeglądarki z właściwym formatem.
Zmienione pliki / kontenery (po wdrożeniu fix)
| Kontener | Plik | Zmiana |
|---|---|---|
s3-v2-v42-prod na bms-1 | /app/dist/apps/storage/storage.controller.js | res.json(filesAdded) → res.json({ result: filesAdded }) |
Rollback
# Oryginalny plik: /tmp/s3-v2-v42-storage-controller.js (backup z bms-1)
docker cp /tmp/s3-v2-v42-storage-controller.js s3-v2-v42-prod:/app/dist/apps/storage/storage.controller.js
docker exec s3-v2-v42-prod pm2 restart s3-v2-v42-prod_backendDługoterminowy fix
Poprawić kontrakt między s3-v2-v42-prod a v42-prod w kodzie TypeScript źródłowym.
Opcja A: fix w s3-v2-v42-prod (zwracaj {result: []})
Opcja B: fix w v42-prod (akceptuj bare array)
Opcja A jest preferowana — v42-prod ma spójne oczekiwanie na body.result[0] w całej bazie kodu.
Dodatkowy incydent — v42-prod crash-loop (2026-07-01T15:45Z)
Po rotacji REDIS_PASSWORD + Wasabi key agent zrestartował v42-prod (nowe środowisko). v42-prod wpadł w crash-loop (506 restartów, uptime 0-1s).
Root cause: publicFiles.helper.ts:16 inicjalizuje multer-s3 na starcie modułu.
multer-s3 wymaga env s3Bucket_api_bucket (i 7 innych pinbox24Public* zmiennych),
które nie były w backend-environment.env — były tylko w s3-v2-environment.env dla
oddzielnego kontenera s3-v2-v42-prod.
Fix: dodanie 8 brakujących zmiennych do backend-environment.env + restart v42-prod.
Backup hotfix script: docs/pinbox24/hotfix-w4-s3-response-format.sh
(re-aplikuj po każdym recreate s3-v2-v42-prod)
Lekcje
- Contract test między usługami — brak testu API między v42-prod a s3-v2-v42-prod. Integration test sprawdzający response format wykryłby to natychmiast.
- Inne s3 odpowiedzi — s3-v32-prod zwraca
{result: []}, s3-v2-v42-prod zwraca[]. Niezgodność typów między wersjami. - HTTP 500 nie loguje URL — v42-prod logger nie loguje 500 z URL (tylko error message). Trudno było zlokalizować 500 bez nginx access logu.
- Env vars split across containers —
publicFiles.helper.tsładuje się przy starcie v42-prod i wymaga zmiennych, które logicznie należą do s3-v2-v42-prod. Każda zmiana env powinna obejmować obie grupy zmiennych. - Hotfix nie przeżywa container recreate — patch na skompilowanym JS w kontenerze ginie przy
docker restart. Backup skryptu w repozytorium jest obowiązkowy.