{"record":{"id":"b5afc636cd5924ff","repo":"janhq/jan","slug":"this-conversation-has-no-user-message-to-respond-t","errorCode":null,"errorMessage":"This conversation has no user message to respond to. Add a message, or regenerate from a turn that includes your question.","messagePattern":"This conversation has no user message to respond to\\. Add a message, or regenerate from a turn that includes your question\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web-app/src/lib/custom-chat-transport.ts","lineNumber":957,"sourceCode":"        this.systemMessage,\n        this.buildMemorySystemInstruction(),\n        this.buildFilesSystemInstruction(messages),\n        this.buildWebSearchSystemInstruction(),\n        this.buildAgentToolsSystemInstruction(),\n      ]\n        .filter((s) => typeof s === 'string' && s.trim().length > 0)\n        .join('\\n\\n') || undefined\n    return typeof raw === 'string' && raw.trim().length > 0 ? raw : undefined\n  }\n\n  /**\n   * Many chat templates (Qwen3.5+) reject a window with no genuine user query\n   * and throw a cryptic Jinja error. Fail early with a clear message when\n   * deletion/eviction has left no real user turn to respond to.\n   */\n  protected assertSendable(messages: UIMessage[]): void {\n    if (!hasGenuineUserQuery(messages)) {\n      throw new Error(\n        'This conversation has no user message to respond to. Add a message, or regenerate from a turn that includes your question.'\n      )\n    }\n  }\n\n  async refreshTools(abortSignal?: AbortSignal) {\n    // Resolve the memory digest before the prompt is built: the builder is\n    // sync, so it reads the snapshot this await guarantees. Error-safe and\n    // cached inside agentTools, so this is one IPC round-trip per store change.\n    await refreshMemoryDigest()\n    if (!this.serviceHub) {\n      this.tools = {}\n      return\n    }\n\n    const toolsRecord: Record<string, Tool> = {}\n\n    // Tool availability is global (shared across all chats).","sourceCodeStart":939,"sourceCodeEnd":975,"githubUrl":"https://github.com/janhq/jan/blob/7205d770c1e097c3daf35a911176410e93bc5564/web-app/src/lib/custom-chat-transport.ts#L939-L975","documentation":"Thrown by assertSendable in custom-chat-transport.ts when the message window handed to a chat template contains no genuine user query. Many chat templates (Qwen 3.5+) throw a cryptic Jinja rendering error when asked to generate without a user turn, so the transport fails early with an actionable message. It protects against windows that were reduced to only assistant/tool messages by deletion or context eviction.","triggerScenarios":"Regenerating from a turn where the last user message was deleted; context-window eviction trimming out all user messages; sending a conversation whose visible turns are assistant-only; programmatic message list construction omitting any user role message.","commonSituations":"Users delete an early question then hit regenerate; long conversations where older user turns were evicted; importing or replaying a chat log that starts with an assistant reply; agents that only append tool/assistant messages.","solutions":["Add a new user message to the conversation before sending.","Regenerate from an earlier turn that still contains a user question.","Adjust eviction/deletion logic so at least one user turn is always retained in the window.","If you manage messages programmatically, check hasGenuineUserQuery(messages) before calling send."],"exampleFix":"// before\nconst window = messages.filter((m) => m.id !== deletedId)\nsend({ messages: window })\n// after\nconst window = messages.filter((m) => m.id !== deletedId)\nif (!hasGenuineUserQuery(window)) {\n  throw new Error('Cannot regenerate: the pruned window has no user message')\n}\nsend({ messages: window })","handlingStrategy":"validation","validationCode":"function hasGenuineUserQuery(messages) {\n  return messages.some((m) => m.role === 'user' && typeof m.content === 'string' ? m.content.trim().length > 0 : Array.isArray(m.content) && m.content.some((p) => p.type === 'text' && p.text.trim()))\n}\nif (!hasGenuineUserQuery(window)) showAddMessageHint()\nelse send({ messages: window })","typeGuard":"const hasUserTurn = (ms) => ms.some((m) => m.role === 'user')","tryCatchPattern":"try { await send({ messages: window }) } catch (e) { if (String(e.message).includes('no user message to respond to')) { promptUserToAddMessage() } else throw e }","preventionTips":["Before regenerate, assert the window still contains a user turn.","Keep at least one user message pinned during context eviction.","In tests, cover delete-then-regenerate flows."],"tags":["chat","validation","message-window","state"],"backgroundTag":"empty-required-field","analyzedSha":"7205d770c1e097c3daf35a911176410e93bc5564","analyzedAt":"2026-09-17T14:27:30.100Z","contentChangedAt":"2026-09-17T14:27:30.100Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}