{"record":{"id":"8898c57506edf9f3","repo":"windmill-labs/windmill","slug":"no-actual-user-message-found-to-restart-from","errorCode":null,"errorMessage":"No actual user message found to restart from","messagePattern":"No actual user message found to restart from","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts","lineNumber":4039,"sourceCode":"\t\t\t\t\timages ?? [],\n\t\t\t\t\tfiles ?? []\n\t\t\t\t)\n\t\t\t}\n\t\t\t// \"Text\", not \"message\": chip edits are the part that does not survive.\n\t\t\tsendUserToast('This session is running in another tab. Your text was kept.', true)\n\t\t\treturn\n\t\t}\n\n\t\t// Resolve the API restart point BEFORE reserving bytes or truncating: a\n\t\t// stale index must fail while nothing has been mutated, or the transcript\n\t\t// would be left truncated with the reservation leaked. A negative index\n\t\t// marks a message whose API counterpart was removed by drop-oldest\n\t\t// compaction — everything before it went too, so restarting from it\n\t\t// restarts from an empty history.\n\t\tconst actualMessageIndex =\n\t\t\tuserMessage.index < 0 ? 0 : userMessage.index < this.messages.length ? userMessage.index : -1\n\t\tif (actualMessageIndex === -1) {\n\t\t\tthrow new Error('No actual user message found to restart from')\n\t\t}\n\n\t\t// Read while both arrays are intact: storedImages pairs the API message with\n\t\t// its transcript entry, and the truncations below drop them.\n\t\tconst sentImages = this.storedImages(displayMessageIndex)\n\n\t\t// Reserve the resent files' bytes across the gap between the edit box\n\t\t// unmounting and the optimistic message landing. A per-resend token owns the\n\t\t// reservation (sendRequest releases only this key) so an unrelated or\n\t\t// concurrent send never clears it. Set before the slice below removes the\n\t\t// message from the transcript, so those bytes are always accounted.\n\t\tconst resendReservationKey = `resend:${createLongHash()}`\n\t\tconst resentFiles = files ?? userMessage.files ?? []\n\t\tthis.setComposerStaged(\n\t\t\tresendReservationKey,\n\t\t\tnull,\n\t\t\tresentFiles.reduce((sum, f) => sum + textByteLength(f.content), 0)\n\t\t)","sourceCodeStart":4021,"sourceCodeEnd":4057,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts#L4021-L4057","documentation":"During restart-from-message, the transcript index is mapped to the underlying API message index. A display message may be a placeholder (index -1) or out of range, meaning its API counterpart was removed by drop-oldest compaction — restarting from it would restart from an empty history — so this error is thrown.","triggerScenarios":"Calling the restart method on a user message whose stored index is -1 (compaction placeholder) or >= messages.length — i.e. the conversation was compacted or truncated after the user captured the message reference.","commonSituations":"Long conversations where drop-oldest compaction removed the oldest API messages while the UI still shows them; attempting to restart from a very old message in a big session; stale UI state after background compaction.","solutions":["Restart from a more recent message whose API entry still exists.","Refresh the session/transcript from the server to get post-compaction state before restarting.","Start a new session instead of restarting from a compacted message.","Disable/raise the drop-oldest compaction threshold if restarts from old messages are required."],"exampleFix":"// before\nawait manager.restartFrom(oldDisplayIndex) // may hit compacted message\n// after\nconst msg = manager.displayMessages[oldDisplayIndex]\nif (msg && msg.index >= 0 && msg.index < manager.messages.length) {\n  await manager.restartFrom(oldDisplayIndex)\n} else {\n  toast('This message can no longer be restarted from — start a new session')\n}","handlingStrategy":"validation","validationCode":"const msg = manager.displayMessages[i]\nif (!msg || msg.index < 0 || msg.index >= manager.messages.length) {\n  throw new Error('Message no longer restartable (compacted)')\n}","typeGuard":null,"tryCatchPattern":"try {\n  await manager.restartFrom(i)\n} catch (e) {\n  if (e.message.includes('restart from')) {\n    toast('This message was compacted; start a new session instead')\n    return\n  }\n  throw e\n}","preventionTips":["Restart only from recent messages in long, compacted sessions.","Refresh the transcript from the server before restart attempts.","Compensate for drop-oldest compaction by disabling restart UI on placeholder messages (index -1)."],"tags":["ai","compaction","history","state","copilot"],"backgroundTag":"history-compaction-invalid-state","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}