{"record":{"id":"ed242765c07e3910","repo":"jackwener/OpenCLI","slug":"json-stringify-payload-slice","errorCode":null,"errorMessage":"申请抖音上传地址响应缺少必要字段: ${JSON.stringify(payload).slice(0, 500)}","messagePattern":"申请抖音上传地址响应缺少必要字段: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/douyin/_shared/vod-upload.js","lineNumber":158,"sourceCode":"  const url = `${VOD_UPLOAD_HOST}?${params.toString()}`;\n  const res = await fetch(url, { headers: computeAws4Headers(url, credentials), signal: AbortSignal.timeout(30000) });\n  const text = await res.text();\n  let payload;\n  try {\n    payload = JSON.parse(text);\n  } catch {\n    throw new CommandExecutionError(`申请抖音上传地址失败，非 JSON 响应: HTTP ${res.status} ${text.slice(0, 300)}`);\n  }\n  const error = payload?.ResponseMetadata?.Error;\n  if (!res.ok || error) {\n    throw new CommandExecutionError(`申请抖音上传地址失败: HTTP ${res.status} ${JSON.stringify(error ?? payload)}`);\n  }\n  const uploadNode = payload?.Result?.InnerUploadAddress?.UploadNodes?.[0];\n  const storeInfo = uploadNode?.StoreInfos?.[0];\n  const videoId = payload?.Result?.Vid || uploadNode?.Vid;\n  const sessionKey = uploadNode?.SessionKey ?? storeInfo?.SessionKey ?? payload?.Result?.SessionKey;\n  if (!uploadNode?.UploadHost || !storeInfo?.StoreUri || !storeInfo?.Auth || !videoId || !sessionKey) {\n    throw new CommandExecutionError(`申请抖音上传地址响应缺少必要字段: ${JSON.stringify(payload).slice(0, 500)}`);\n  }\n  return {\n    video_id: videoId,\n    tos_upload_url: `https://${uploadNode.UploadHost}/${storeInfo.StoreUri}`,\n    auth: storeInfo.Auth,\n    session_key: sessionKey,\n    upload_header: uploadNode.UploadHeader ?? {},\n    user_id: credentials.user_id ?? '',\n  };\n}\n\n\nexport async function commitVideoUploadInner(uploadInfo, credentials) {\n  if (!uploadInfo?.session_key) {\n    throw new CommandExecutionError('抖音上传提交缺少 SessionKey');\n  }\n  const params = new URLSearchParams({\n    Action: 'CommitUploadInner',","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/douyin/_shared/vod-upload.js#L140-L176","documentation":"The ApplyUploadInner call succeeded (HTTP ok, no ResponseMetadata.Error) but the response lacks required upload fields: UploadHost, StoreUri, StoreInfo.Auth, Vid, or SessionKey. The library cannot construct the TOS upload URL or commit key without them, so it fails fast with a truncated payload dump (first 500 chars).","triggerScenarios":"payload.Result.InnerUploadAddress.UploadNodes is empty or missing StoreInfos[0]; Vid and SessionKey absent from all expected locations — typically schema drift or an empty/degraded success response.","commonSituations":"Douyin changed the response nesting (renamed InnerUploadAddress/UploadNodes/StoreInfos); zero upload nodes returned because the space/quota is misconfigured; a partially migrated API version returns a new shape.","solutions":["Inspect the payload dump in the message to see which fields are missing and where the data actually lives.","Update the extraction paths in applyVideoUploadInner to match the current response schema if Douyin changed it.","Verify the account/space still allows inner uploads (empty UploadNodes may indicate a permission/quota problem).","Retry once — occasionally a degraded response is transient.","Update the CLI/library if a Douyin API version bump altered the upload address structure."],"exampleFix":"// before\nconst uploadNode = payload?.Result?.InnerUploadAddress?.UploadNodes?.[0];\n// after\nconst uploadNode = payload?.Result?.InnerUploadAddress?.UploadNodes?.[0]\n  ?? payload?.Result?.UploadAddress?.UploadNodes?.[0]; // tolerate schema variants","handlingStrategy":"validation","validationCode":"function hasUploadAddress(payload) {\n  const node = payload?.Result?.InnerUploadAddress?.UploadNodes?.[0];\n  const store = node?.StoreInfos?.[0];\n  return Boolean(node?.UploadHost && store?.StoreUri && store?.Auth\n    && (payload?.Result?.Vid || node?.Vid)\n    && (node?.SessionKey ?? store?.SessionKey ?? payload?.Result?.SessionKey));\n}","typeGuard":"null","tryCatchPattern":"try {\n  uploadInfo = await applyVideoUploadInner(fileSize, creds);\n} catch (e) {\n  if (/缺少必要字段/.test(e.message)) {\n    console.error('实际响应:', e.message); // adapt extraction paths to schema drift\n  }\n  throw e;\n}","preventionTips":["Snapshot real responses in tests to detect Douyin schema drift early.","Never proceed to PUT/commit when apply fails — abort the pipeline.","Check account/space upload quota if UploadNodes comes back empty.","Keep the CLI updated against Douyin API version changes."],"tags":["douyin","schema-validation","missing-field","upload-address"],"backgroundTag":"missing-required-field","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}