{"record":{"id":"9918831e7635ced1","repo":"deepseek-ai/deepseek-harness","slug":"conversation-serializedraftimages-one-or-more-dra","errorCode":null,"errorMessage":"conversation.serializeDraftImages: one or more draft images are no longer available","messagePattern":"conversation\\.serializeDraftImages: one or more draft images are no longer available","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/client/ui-conversation/src/client/service.ts","lineNumber":203,"sourceCode":"    const attachments: ComposerAttachment[] = []\n    for (const id of ids) {\n      const attachment = this.draftAttachments.get(id)\n      if (attachment !== undefined) attachments.push(attachment)\n    }\n    return attachments\n  }\n\n  /**\n   * Serialize ordered draft images to command-submit wire payloads without\n   * sending or releasing them (the composer releases only after the command\n   * settles successfully).\n   * @param imageIds - ordered draft-local attachment ids.\n   * @returns base64 payloads in id order.\n   */\n  async serializeDraftImages(imageIds: readonly DraftAttachmentId[]): Promise<readonly SubmitImageAttachment[]> {\n    const attachments = this.draftImages(imageIds)\n    if (attachments.length !== imageIds.length) {\n      throw new Error('conversation.serializeDraftImages: one or more draft images are no longer available')\n    }\n    return Promise.all(attachments.map(attachment => this.encodeImage(attachment.file)))\n  }\n\n  /**\n   * Release one browser-owned draft image and preview URL.\n   * @param id - draft attachment id.\n   */\n  releaseDraftImage(id: DraftAttachmentId): void {\n    const attachment = this.draftAttachments.get(id)\n    if (attachment === undefined) return\n    this.draftAttachments.delete(id)\n    this.createdImageUrls.delete(attachment.previewUrl)\n    revokePreview(attachment.previewUrl)\n  }\n\n  /**\n   * Release a set of browser-owned draft images.","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/deepseek-ai/deepseek-harness/blob/b150a551b8d465e31e418e1b2eaf5e79bbb7d28e/packages/client/ui-conversation/src/client/service.ts#L185-L221","documentation":"serializeDraftImages() resolves ordered draft ids to base64 wire payloads for slash-command submission (the composer releases images only after the command settles successfully). Like sendSession, any id that no longer lives in draftAttachments throws immediately — the command is never sent with a partial image set.","triggerScenarios":"A slash-command submit carrying images where an id was already released: an earlier successful command released its images, the chip was removed mid-flight, or the ids predate a remount of the runtime that owns attachments.","commonSituations":"Retrying a command whose first run released the images on success; UI chip state drifting from the runtime attachment map after a failed outcome kept the draft.","solutions":["Validate id liveness (draftImages length check) and refresh the id list from the composer before serializing","Retry the command without the released images if the command tolerates an empty image set","Audit the release policy so images release exactly once, on settle"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const live = service.draftImages(imageIds)\nif (live.length !== imageIds.length) {\n  throw new Error('image no longer attached; re-attach before submitting')\n}\nreturn service.serializeDraftImages(live.map(a => a.id))","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Serialize draft images and execute the command inside one attempt so ids cannot go stale between the steps","Release command images exactly once, on settle","Mirror releases into UI chip state so the two never drift"],"tags":["attachments","slash-commands","race-condition","draft"],"backgroundTag":"stale-id-reference","analyzedSha":"b150a551b8d465e31e418e1b2eaf5e79bbb7d28e","analyzedAt":"2026-08-24T18:12:29.105Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}