{"record":{"id":"3af745aaff7264a9","repo":"BigPizzaV3/CodexPlusPlus","slug":"codex-renderer-inject","errorCode":null,"errorMessage":"无法读取完整 Codex 会话文件","messagePattern":"无法读取完整 Codex 会话文件","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"assets/inject/renderer-inject.js","lineNumber":7585,"sourceCode":"    if (!markdown || !session) {\n      showToast(\"当前会话还没有可分享的消息\", null);\n      return;\n    }\n    const shareWindow = window.open(\"about:blank\", \"_blank\");\n    const button = document.querySelector(`.${sessionShareButtonClass}`);\n    if (button) {\n      button.disabled = true;\n      button.setAttribute(\"aria-busy\", \"true\");\n      button.textContent = \"正在创建…\";\n    }\n    try {\n      let shareDocument = session;\n      const nativeSession = await postJson(\"/session/export\", {\n        session_id: ref.session_id,\n        title: session.title,\n      });\n      if (nativeSession?.status !== \"ok\" || nativeSession.kind !== \"codex-rollout\" || typeof nativeSession.content !== \"string\") {\n        throw new Error(nativeSession?.message || \"无法读取完整 Codex 会话文件\");\n      }\n      shareDocument = { ...nativeSession, title: session.title };\n      const encrypted = await encryptSessionShare(JSON.stringify(shareDocument));\n      const payload = { ttl: 604800, encrypted: encrypted.encrypted };\n      let result;\n      let baseUrl = codexPlusShareBaseUrl;\n      try {\n        result = await postJson(\"/share/create\", payload);\n        if (result?.id) {\n          baseUrl = codexPlusShareBaseUrl;\n        } else if (result?.status !== \"failed\") {\n          throw new Error(result?.message || \"创建分享失败\");\n        }\n      } catch (_) {\n        result = null;\n      }\n      if (!result?.id) {\n        let response;","sourceCodeStart":7567,"sourceCodeEnd":7603,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/assets/inject/renderer-inject.js#L7567-L7603","documentation":"This error is thrown by the CodexPlusPlus session-share flow in renderer-inject.js when exporting a Codex session for sharing. The code calls POST /session/export and expects a response with status \"ok\", kind \"codex-rollout\", and a string content field (the raw rollout file). If any of those checks fail — or the endpoint itself reports an error message — the flow aborts with \"无法读取完整 Codex 会话文件\" (\"cannot read the complete Codex session file\"), because the encrypted share payload cannot be built without the full session document.","triggerScenarios":"Calling the share/export action when: the /session/export endpoint returns status !== \"ok\"; the response kind is not \"codex-rollout\" (e.g. truncated or partial export); content is missing or not a string; or the backend returns its own error message which is used as the thrown message.","commonSituations":"The Codex rollout/session file was rotated, deleted, or truncated on disk before export; session_id points to an expired or non-existent session; the backend proxy cannot read the rollout file due to permissions; a version mismatch where the export endpoint changed its response shape (kind/content fields).","solutions":["Verify the session_id exists and the rollout file on disk is complete (not truncated); re-open or re-select the session and retry the share.","Check the /session/export backend response (status/kind/content) — if status is not \"ok\", use its message field to identify the underlying server-side cause.","Confirm file read permissions on the Codex sessions directory so the backend can read the full rollout file.","Upgrade CodexPlusPlus client and backend together so the export response shape (kind: \"codex-rollout\", string content) matches."],"exampleFix":"// before: assume export always succeeds\nconst nativeSession = await postJson(\"/session/export\", { session_id });\nshareDocument = { ...nativeSession, title: session.title };\n// after: guard the response shape before using it\nconst nativeSession = await postJson(\"/session/export\", { session_id });\nif (nativeSession?.status !== \"ok\" || nativeSession.kind !== \"codex-rollout\" || typeof nativeSession.content !== \"string\") {\n  throw new Error(nativeSession?.message || \"无法读取完整 Codex 会话文件\");\n}","handlingStrategy":"type-guard","validationCode":"const res = await postJson(\"/session/export\", { session_id });\nif (res?.status !== \"ok\" || res.kind !== \"codex-rollout\" || typeof res.content !== \"string\") {\n  throw new Error(res?.message || \"会话导出不可用\");\n}","typeGuard":"function isCodexRolloutExport(v) {\n  return v != null && typeof v === \"object\" && v.status === \"ok\" && v.kind === \"codex-rollout\" && typeof v.content === \"string\";\n}","tryCatchPattern":"try {\n  shareDocument = await exportSession(ref.session_id, session.title);\n} catch (e) {\n  console.error(\"会话导出失败，已取消分享:\", e);\n  showToast(\"无法读取完整 Codex 会话文件，请确认会话仍存在后重试\");\n  return;\n}","preventionTips":["Always validate the export response shape before consuming content.","Keep client and backend versions in sync so the response contract (kind/content) stays stable.","Check session/rollout file existence server-side and return a precise message instead of a truncated export."],"tags":["session-export","unexpected-response-shape","network","file-read"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}