{"record":{"id":"34a984d57e31d420","repo":"plandex-ai/plandex","slug":"stream-timeout-due-to-inactivity-the-ai-model-s","errorCode":null,"errorMessage":"stream timeout due to inactivity: The AI model (%s/%s) is not responding","messagePattern":"stream timeout due to inactivity: The AI model \\((.+?)/(.+?)\\) is not responding","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/model/plan/tell_stream_main.go","lineNumber":103,"sourceCode":"\nmainLoop:\n\tfor {\n\t\tselect {\n\t\tcase <-active.Ctx.Done():\n\t\t\t// The main modelContext was canceled (not the timer)\n\t\t\tlog.Println(\"\\nTell: stream canceled\")\n\t\t\tstate.execHookOnStop(false)\n\t\t\treturn\n\t\tcase <-timer.C:\n\t\t\t// Timer triggered because no new chunk was received in time\n\t\t\tlog.Println(\"\\nTell: stream timeout due to inactivity\")\n\t\t\tif streamFinished {\n\t\t\t\tlog.Println(\"Tell stream finished—timed out waiting for usage chunk\")\n\t\t\t\tstate.execHookOnStop(false)\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\tres := state.onError(onErrorParams{\n\t\t\t\t\tstreamErr: fmt.Errorf(\"stream timeout due to inactivity: The AI model (%s/%s) is not responding\", modelProvider, modelName),\n\t\t\t\t\tstoreDesc: true,\n\t\t\t\t\tcanRetry:  active.CurrentReplyContent == \"\", // if there was no output yet, we can retry\n\t\t\t\t})\n\n\t\t\t\tif res.shouldReturn {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif res.shouldContinueMainLoop {\n\t\t\t\t\tcontinue mainLoop\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase err := <-streamErrCh:\n\t\t\tlog.Printf(\"listenStream - received from streamErrCh: %v\\n\", err)\n\n\t\t\tif err.Error() == \"context canceled\" {\n\t\t\t\tlog.Println(\"Tell: stream context canceled\")\n\t\t\t\tstate.execHookOnStop(false)","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/model/plan/tell_stream_main.go#L85-L121","documentation":"listenStream in tell_stream_main.go guards against a stalled LLM stream with a first-token/inactivity timer (sized by totalRequestTokens). If no chunk arrives from the model within the timeout and the stream hasn't finished, it raises 'stream timeout due to inactivity: The AI model (provider/model) is not responding' via state.onError with storeDesc=true and canRetry set only when zero output was received. This is a client-side watchdog, not a model error response.","triggerScenarios":"stream.Recv() blocks beyond firstTokenTimeout: provider hang, network black hole (no TCP reset), overloaded model backend, or a locally-hosted model that is still loading.","commonSituations":"Self-hosted/local model cold-start exceeding the timeout; provider outage or rate-limit stall; network middlebox silently dropping the connection; very large request causing long queue time.","solutions":["Retry the request (automatically allowed when no output was produced yet, canRetry=true)","Check the named provider/model status — it is embedded in the message","For local models, increase the first-token timeout or warm up the model before requests","Verify network path/proxy isn't silently dropping idle connections"],"exampleFix":"// before\nres := state.onError(onErrorParams{\n    streamErr: fmt.Errorf(\"stream timeout due to inactivity: The AI model (%s/%s) is not responding\", modelProvider, modelName),\n    storeDesc: true,\n    canRetry:  active.CurrentReplyContent == \"\",\n})\n// after\n// warm up / health-check the model before long requests, and make timeout configurable\ntimeout := firstTokenTimeout(state.totalRequestTokens, baseModelConfig.LocalOnly)\nlog.Printf(\"listenStream: inactivity timeout %s for %s/%s\", timeout, modelProvider, modelName)\nres := state.onError(onErrorParams{\n    streamErr: fmt.Errorf(\"stream timeout due to inactivity: The AI model (%s/%s) is not responding\", modelProvider, modelName),\n    storeDesc: true,\n    canRetry:  active.CurrentReplyContent == \"\",\n})","handlingStrategy":"retry","validationCode":"if baseModelConfig.LocalOnly && !isLocalModelReady(provider, model) {\n    return fmt.Errorf(\"local model %s/%s not ready; skipping request\", provider, model)\n}","typeGuard":null,"tryCatchPattern":"case <-timer.C:\n    res := state.onError(onErrorParams{\n        streamErr: fmt.Errorf(\"stream timeout due to inactivity: The AI model (%s/%s) is not responding\", modelProvider, modelName),\n        storeDesc: true,\n        canRetry:  active.CurrentReplyContent == \"\",\n    })\n    if res.shouldReturn {\n        return\n    }","preventionTips":["Enable retries only when no partial output exists to avoid duplicated replies","Size the inactivity timeout by request size and model type (local vs hosted)","Health-check/warm up local models before sending large requests","Monitor provider stall rates and set upstream keep-alives to avoid silent connection drops"],"tags":["timeout","llm","streaming","inactivity","retry"],"backgroundTag":"llm-stream-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"}