{"record":{"id":"484af0af42e15a72","repo":"deepseek-ai/deepseek-harness","slug":"conversation-sendsession-one-or-more-draft-images","errorCode":null,"errorMessage":"conversation.sendSession: one or more draft images are no longer available","messagePattern":"conversation\\.sendSession: 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":154,"sourceCode":"  /**\n   * Submit ordered draft images with text through one host admission.\n   * @param session - target session.\n   * @param text - serialized prompt text.\n   * @param imageIds - ordered draft-local attachment ids.\n   * @param mode - queue or steer delivery selected by composer policy.\n   * @param signal - optional cancellation for the complete Host admission.\n   * @returns the Host admission outcome; local attachment preparation failures reject.\n   */\n  async sendSession(\n    session: SessionFace,\n    text: string,\n    imageIds: readonly DraftAttachmentId[],\n    mode: InputSubmitMode,\n    signal?: AbortSignal,\n  ): Promise<SubmitOutcome> {\n    const attachments = this.draftImages(imageIds)\n    if (attachments.length !== imageIds.length) {\n      throw new Error('conversation.sendSession: one or more draft images are no longer available')\n    }\n    const uploaded = await this.serializeImages(attachments.map(attachment => attachment.file))\n    const content = [...uploaded, ...(text === '' ? [] : [{ type: 'text' as const, text }])]\n    const result = await session.prompt(content, mode, signal)\n    if (!result.ok) return { kind: 'error' }\n    this.releaseDraftImages(attachments)\n    return { kind: 'success' }\n  }\n\n  /**\n   * Create runtime-only draft images and their object URLs.\n   * @param files - browser files to register after MIME validation.\n   * @returns ordered draft descriptors.\n   */\n  createDraftImages(files: readonly File[]): readonly ComposerAttachment[] {\n    for (const file of files) imageMediaType(file.type)\n    return files.map((file) => {\n      const attachment = browserDraftAttachment(file)","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/deepseek-ai/deepseek-harness/blob/b150a551b8d465e31e418e1b2eaf5e79bbb7d28e/packages/client/ui-conversation/src/client/service.ts#L136-L172","documentation":"sendSession() resolves the submitted draft-image ids against the runtime-owned draftAttachments map. Draft attachments are runtime-only: ids released by a prior successful submit (releaseDraftImages), removed from the composer, or lost to a remount simply no longer resolve. Any length mismatch throws before upload or prompt, so nothing is partially sent.","triggerScenarios":"Submitting the same image ids twice — the first success already released them via releaseDraftImages; removing an image chip while its submit is in flight; or using ids captured before a remount (HMR, panel re-mount) against a fresh runtime that never registered them.","commonSituations":"Double-Enter races in the composer; X-ing an image mid-submission; hot reload landing between draft persistence and send.","solutions":["Reconcile ids against live attachments before submitting (draftImages(ids).length check) and re-attach files when short","Latch Enter while a submit attempt is pending so double-submit cannot occur","On remount, rebuild draft attachments from the File objects instead of reusing stale ids"],"exampleFix":"// before\nawait service.sendSession(session, text, imageIds, mode)\n// after\nif (service.draftImages(imageIds).length !== imageIds.length) {\n  imageIds = currentComposerIds() // or block the submit with a notice\n}\nawait service.sendSession(session, text, imageIds, mode)","handlingStrategy":"validation","validationCode":"if (service.draftImages(imageIds).length !== imageIds.length) {\n  imageIds = liveComposerIds()\n  if (imageIds.length === 0) return { kind: 'error' }\n}\nawait service.sendSession(session, text, imageIds, mode)","typeGuard":null,"tryCatchPattern":"try {\n  await service.sendSession(session, text, imageIds, mode)\n} catch (error) {\n  if (isDraftImageGone(error)) refreshComposerAttachments() // re-attach and let the user retry\n  throw error\n}","preventionTips":["Treat every DraftAttachmentId as single-submit; never reuse ids after a successful send","Latch the composer while an attempt is pending to kill double-Enter","On remount, rebuild attachments from the File objects instead of reusing stale ids"],"tags":["attachments","race-condition","draft","ui"],"backgroundTag":"stale-id-reference","analyzedSha":"b150a551b8d465e31e418e1b2eaf5e79bbb7d28e","analyzedAt":"2026-08-24T18:12:29.105Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}