{"record":{"id":"5c0851367356846f","repo":"odysseus-dev/odysseus","slug":"await-res-text","errorCode":null,"errorMessage":"await res.text()","messagePattern":"await res\\.text\\(\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"static/js/chat.js","lineNumber":241,"sourceCode":"    const pill = document.getElementById('chat-context-pill');\n    if (!pill) return;\n    pill.addEventListener('click', (e) => {\n      e.preventDefault();\n      e.stopPropagation();\n      _showContextHeaderPopup();\n    });\n  }\n\n  export async function compactCurrentChatContext() {\n    const sm = _liveSessionModule();\n    const sid = sm && sm.getCurrentSessionId && sm.getCurrentSessionId();\n    if (!sid) {\n      uiModule.showToast('Open a chat first');\n      return false;\n    }\n    try {\n      const res = await fetch(`/api/session/${encodeURIComponent(sid)}/compact`, { method: 'POST' });\n      if (!res.ok) throw new Error(await res.text());\n      uiModule.showToast('Context compacted');\n      _closeContextHeaderPopup();\n      if (sm && sm.selectSession) await sm.selectSession(sid, { keepSidebar: true, showLoading: false });\n      refreshChatContextHeader('compact');\n      return true;\n    } catch (err) {\n      uiModule.showError(`Compact failed: ${err.message || err}`);\n      return false;\n    }\n  }\n  try { window.compactCurrentChatContext = compactCurrentChatContext; } catch (_) {}\n\n  export async function refreshChatContextHeader(reason = '') {\n    _bindContextHeaderPill();\n    const pill = document.getElementById('chat-context-pill');\n    if (!pill) return;\n    const sm = _liveSessionModule();\n    const sid = sm && sm.getCurrentSessionId && sm.getCurrentSessionId();","sourceCodeStart":223,"sourceCodeEnd":259,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/chat.js#L223-L259","documentation":"HTTP 400 from PUT /{file_id}/vision when the file_id path parameter fails upload_handler.validate_upload_id() — the same ^[0-9a-fA-F]{32}(\\.[A-Za-z0-9]+)?$ gate used across the upload routes. On this write route it runs before the index lookup and before any auth check, so a malformed id is always reported as 400 regardless of credentials.","triggerScenarios":"PUT /api/upload/{id}/vision with an id that is not 32-char hex (+optional single extension): truncated id, traversal string, or the client sending a filename.","commonSituations":"Editing OCR text in the UI where the attachment record was built from the original filename; id mutated by an upstream URL builder; stale client code predating the id format.","solutions":["Send the upload id from the upload response's `id` field verbatim.","Client-side validate with ^[0-9a-fA-F]{32}(\\.[A-Za-z0-9]+)?$ before PUT.","Log the outbound URL when debugging — most cases are a wrong field or truncation.","Keep exactly one extension segment if appending one."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const UPLOAD_ID_RE = /^[0-9a-fA-F]{32}(\\.[A-Za-z0-9]+)?$/;\nif (!UPLOAD_ID_RE.test(fileId)) return saveRejected('bad id');","typeGuard":"const isUploadId = (v) => typeof v === 'string' && /^[0-9a-fA-F]{32}(\\.[A-Za-z0-9]+)?$/.test(v);","tryCatchPattern":null,"preventionTips":["Bind the editor to the attachment's upload id, not its display filename","Run the id regex before issuing the PUT"],"tags":["validation","http-400","upload-id","vision"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}