{"record":{"id":"74584b681f3d7408","repo":"plandex-ai/plandex","slug":"error-getting-plan-modelcontext-v-74584b","errorCode":null,"errorMessage":"error getting plan modelContext: %v","messagePattern":"error getting plan modelContext: (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"app/server/model/plan/tell_load.go","lineNumber":145,"sourceCode":"\n\t\t\t\t\treturn nil\n\t\t\t\t})\n\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"Error renaming plan: %v\\n\", err)\n\t\t\t\t\terrCh <- fmt.Errorf(\"error renaming plan: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\terrCh <- nil\n\t\t}()\n\n\t\tgo func() {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tlog.Printf(\"panic in getPlanContexts: %v\\n%s\", r, debug.Stack())\n\t\t\t\t\terrCh <- fmt.Errorf(\"error getting plan modelContext: %v\", r)\n\t\t\t\t\truntime.Goexit() // don't allow outer function to continue and double-send to channel\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tif iteration > 0 || missingFileResponse != \"\" {\n\t\t\t\tmodelContext = active.Contexts\n\t\t\t} else {\n\t\t\t\tres, err := db.GetPlanContexts(currentOrgId, planId, true, false)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"Error getting plan modelContext: %v\\n\", err)\n\t\t\t\t\terrCh <- fmt.Errorf(\"error getting plan modelContext: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tlog.Printf(\"[TellLoad] Tell plan - loadTellPlan - modelContext: %v\\n\", len(modelContext))\n\t\t\t\t// for _, part := range modelContext {\n\t\t\t\t// \tlog.Printf(\"[TellLoad] Tell plan - loadTellPlan - part: %s - %s - %s - %d tokens\\n\", part.ContextType, part.Name, part.FilePath, part.NumTokens)\n\t\t\t\t// }","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/model/plan/tell_load.go#L127-L163","documentation":"This error is produced by the recover() handler in the getPlanContexts goroutine: any panic raised while assembling the plan's model context is converted into this error, sent to errCh, and the goroutine exits via runtime.Goexit so the outer function does not double-send. It means the context-loading goroutine crashed unexpectedly rather than returning a normal error — typically a nil map/slice or index-out-of-range while building modelContext.","triggerScenarios":"A panic in the getPlanContexts goroutine — e.g. dereferencing a nil active plan/context, indexing into an empty modelContext slice, or a nil-pointer in code operating on active.Contexts or db results before the deferred recover fires.","commonSituations":"Concurrent map/slice access on ActivePlan while other goroutines mutate it; a code change introduced a nil dereference on a freshly created plan (iteration == 0 and no missingFileResponse); library upgrade changing GetPlanContexts return shape handled without nil checks.","solutions":["Read the 'panic in getPlanContexts' log line with its stack trace to locate the panicking code","Add nil/range checks around active.Contexts and the GetPlanContexts result before use","Fix the underlying nil-pointer or index bug identified by the stack trace","Re-run the request — this is a software defect, not a transient state","Consider making the goroutine return errors instead of panicking so failures are typed and testable"],"exampleFix":"// before\nif iteration > 0 || missingFileResponse != \"\" {\n    modelContext = active.Contexts\n}\n// after\nif iteration > 0 || missingFileResponse != \"\" {\n    if active == nil || active.Contexts == nil {\n        errCh <- fmt.Errorf(\"no active plan contexts available\")\n        return\n    }\n    modelContext = active.Contexts\n}","handlingStrategy":"try-catch","validationCode":"// guard before the goroutine body runs\nif active == nil || active.Ctx == nil {\n    return fmt.Errorf(\"no active plan available for context loading\")\n}\nif iteration > 0 || missingFileResponse != \"\" {\n    if active.Contexts == nil {\n        return fmt.Errorf(\"active plan has no cached contexts\")\n    }\n}","typeGuard":"func hasActiveContexts(ap *types.ActivePlan) bool {\n    return ap != nil && ap.Contexts != nil && len(ap.Contexts) > 0\n}","tryCatchPattern":"go func() {\n    defer func() {\n        if r := recover(); r != nil {\n            log.Printf(\"panic in getPlanContexts: %v\\n%s\", r, debug.Stack())\n            errCh <- fmt.Errorf(\"error getting plan modelContext: %v\", r)\n            runtime.Goexit()\n        }\n    }()\n    ... // goroutine body\n}()\n// consumer side:\nif err := <-errCh; err != nil {\n    if strings.HasPrefix(err.Error(), \"error getting plan modelContext\") {\n        // treat as unrecoverable software defect: fail request, alert with stack trace\n    }\n}","preventionTips":["Nil-check active plan fields (Contexts, Ctx) before dereferencing in goroutines","Avoid unsynchronized reads/writes of ActivePlan shared across goroutines","Prefer returning errors over panics so failures are typed and testable","Always pair recover() with debug.Stack() logging as done here, and use runtime.Goexit to prevent double channel sends","Add unit tests covering iteration==0 with empty/missing context data"],"tags":["panic","goroutine","recovery","context"],"backgroundTag":"goroutine-panic-recovered","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"}