{"record":{"id":"0cef91b4315222ef","repo":"plandex-ai/plandex","slug":"timeout-waiting-for-missing-file-choice","errorCode":null,"errorMessage":"timeout waiting for missing file choice","messagePattern":"timeout waiting for missing file choice","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"app/server/model/plan/tell_stream_processor.go","lineNumber":694,"sourceCode":"\t// log.Printf(\"Current reply content: %s\\n\", active.CurrentReplyContent)\n\n\t// stop stream for now\n\tactive.CancelModelStreamFn()\n\n\tlog.Printf(\"Stopped stream for missing file: %s\\n\", currentFile)\n\n\t// wait for user response to come in\n\tvar userChoice shared.RespondMissingFileChoice\n\tselect {\n\tcase <-active.Ctx.Done():\n\t\tlog.Println(\"Context cancelled while waiting for missing file response\")\n\t\tstate.execHookOnStop(false)\n\t\treturn processChunkResult{shouldReturn: true}\n\n\tcase <-time.After(30 * time.Minute): // long timeout here since we're waiting for user input\n\t\tlog.Println(\"Timeout waiting for missing file choice\")\n\t\tstate.onError(onErrorParams{\n\t\t\tstreamErr: fmt.Errorf(\"timeout waiting for missing file choice\"),\n\t\t\tstoreDesc: true,\n\t\t})\n\t\treturn processChunkResult{}\n\n\tcase userChoice = <-active.MissingFileResponseCh:\n\t}\n\n\tlog.Printf(\"User choice for missing file: %s\\n\", userChoice)\n\n\tactive.ResetModelCtx()\n\n\tUpdateActivePlan(planId, branch, func(ap *types.ActivePlan) {\n\t\tap.MissingFilePath = \"\"\n\t\tap.CurrentReplyContent = replyParser.GetReplyForMissingFile()\n\t})\n\n\tlog.Println(\"Continuing stream\")\n","sourceCodeStart":676,"sourceCodeEnd":712,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/model/plan/tell_stream_processor.go#L676-L712","documentation":"Raised in handleMissingFile inside processChunk when the model asked the user to choose how to handle a missing file (prompt pushed to active.MissingFileResponseCh) and no answer arrived within 30 minutes. The processor intentionally uses a long timeout because it blocks on human input; on expiry it calls state.onError with storeDesc:true and stops processing (empty processChunkResult, hookOnStop already fired).","triggerScenarios":"The stream processor hit a missing-file prompt and waited on active.MissingFileResponseCh; the user never answered within 30 minutes (client closed, tab backgrounded, user walked away, or the UI never rendered the prompt).","commonSituations":"User leaves the session idle overnight after a missing-file prompt appears; client disconnects so the response channel is never written; a frontend bug swallows the missing-file dialog so nothing can send to MissingFileResponseCh; long-running agent session left unattended.","solutions":["Answer the missing-file prompt (or re-send the user's choice into active.MissingFileResponseCh) within the 30-minute window.","Increase the 30*time.After duration if longer human decision time is expected.","Make the UI reliably surface the missing-file dialog so the user can respond.","Detect client disconnect and cancel the wait early instead of blocking 30 minutes on a dead session.","Re-run the request after the timeout — the stored error description marks the session as failed."],"exampleFix":"// before\n\tcase <-time.After(30 * time.Minute):\n\t\tlog.Println(\"Timeout waiting for missing file choice\")\n// after: extend window and support cancellation on client disconnect\n\tcase <-time.After(2 * time.Hour):\n\t\tstate.onError(onErrorParams{streamErr: fmt.Errorf(\"timeout waiting for missing file choice\"), storeDesc: true})\n\t\treturn processChunkResult{}\n\tcase <-active.ClientDisconnected:\n\t\tstate.execHookOnStop(false)\n\t\treturn processChunkResult{shouldReturn: true}","handlingStrategy":"fallback","validationCode":"// before blocking: ensure there is a live client able to answer the prompt\nif active.MissingFileResponseCh == nil || !active.HasLiveClient() {\n    return processChunkResult{shouldReturn: true} // skip waiting entirely\n}","typeGuard":"func canAwaitUserChoice(active *StreamState) bool {\n    return active != nil && active.MissingFileResponseCh != nil && active.ClientConnected\n}","tryCatchPattern":"select {\ncase ch := <-active.MissingFileResponseCh:\n    // handle choice\ncase <-time.After(30 * time.Minute):\n    log.Println(\"Timeout waiting for missing file choice\")\n    state.onError(onErrorParams{streamErr: fmt.Errorf(\"timeout waiting for missing file choice\"), storeDesc: true})\n    return processChunkResult{}\n}","preventionTips":["Always render the missing-file dialog promptly in the UI","Auto-resolve with a sensible default (e.g. skip file) instead of blocking on humans","Tie the wait to client-lifetime context cancellation, not only a timer","Log when the prompt is issued so idle sessions can be detected early"],"tags":["timeout","user-input","streaming"],"backgroundTag":"awaiting-user-input-timeout","analyzedSha":"e2d772072efadbe41d2946d97d79be55532dbab5","analyzedAt":"2026-09-05T20:56:53.631Z","contentChangedAt":"2026-09-05T20:56:53.631Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}