{"record":{"id":"afb1bbca5213d1ce","repo":"odysseus-dev/odysseus","slug":"http-saveres-status-errbody-substring-0-120","errorCode":null,"errorMessage":"HTTP ${saveRes.status}: ${errBody.substring(0, 120)}","messagePattern":"HTTP (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/galleryEditor.js","lineNumber":3698,"sourceCode":"    const flat = flatten();\n    const ext = (state.originalExt || 'png').toLowerCase();\n    const isJpeg = ext === 'jpg' || ext === 'jpeg';\n    const mime = isJpeg ? 'image/jpeg' : 'image/png';\n    const quality = isJpeg ? 0.92 : undefined;\n    blob = await new Promise((resolve, reject) => {\n      flat.toBlob(b => b ? resolve(b) : reject(new Error('Canvas encode failed')), mime, quality);\n    });\n    const formData = new FormData();\n    formData.append('file', blob, `edited.${isJpeg ? 'jpg' : 'png'}`);\n\n    const saveRes = await fetch(`${API_BASE}/api/gallery/upload`, {\n      method: 'POST',\n      credentials: 'same-origin',\n      body: formData,\n    });\n    if (!saveRes.ok) {\n      const errBody = await saveRes.text().catch(() => '');\n      throw new Error(`HTTP ${saveRes.status}: ${errBody.substring(0, 120)}`);\n    }\n    const totalMs = Math.round(performance.now() - t0);\n    window.dispatchEvent(new CustomEvent('gallery-refresh'));\n    if (uiModule) uiModule.showToast(`Saved copy to gallery (${(blob.size / 1024 / 1024).toFixed(1)}MB · ${(totalMs / 1000).toFixed(1)}s)`, 4000);\n    savedOk = true;\n    if (state.draftId) {\n      _clearDraftServer(state.draftId);\n      state.draftId = null;\n    }\n  } catch (e) {\n    console.error('[save-as-copy] error:', e);\n    const sizeMB = blob ? ` (${(blob.size / 1024 / 1024).toFixed(1)}MB)` : '';\n    let msg = e?.message || 'unknown';\n    if (e?.name === 'TypeError' || /fetch|network|load failed/i.test(msg)) {\n      msg = `network dropped${sizeMB} — check connection`;\n    } else {\n      msg += sizeMB;\n    }","sourceCodeStart":3680,"sourceCodeEnd":3716,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/galleryEditor.js#L3680-L3716","documentation":"Thrown by 'Save as copy' in galleryEditor.js when POST /api/gallery/upload returns non-ok. Unlike the replace flow it reads the raw body as text and truncates to 120 chars, so HTML error pages from proxies appear inline. On success it clears the server-side draft; on failure the draft is kept.","triggerScenarios":"Uploading an edited copy when the gallery upload directory is unwritable; a 413 from a fronting proxy on a large PNG; a 500 from an upload handler (disk full, filename collision); auth cookie missing.","commonSituations":"Same body-size pitfalls as error 84; galleries on network mounts that dropped; server restarted mid-edit so the upload endpoint's temp dir is gone.","solutions":["Reduce output size (export as JPEG) and retry if the status is 413.","Check that the server's gallery storage path exists and is writable.","Free disk space if the body text mentions ENOSPC/write failure.","Refresh auth and retry; the editor draft is preserved so no work is lost."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { if (!saveRes.ok) throw new Error(`HTTP ${saveRes.status}`); } catch (e) { keep draft on server; suggest re-encode as JPEG and retry; }","preventionTips":["Only clear the server draft after a confirmed successful upload","Truncate but also log the full error body for diagnosis","Shrink exports (JPEG quality) before upload on 413"],"tags":["upload","http","gallery","storage","editor"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}