{"record":{"id":"3fd1508b6c4b3761","repo":"plandex-ai/plandex","slug":"panic-in-updatecontexts-v-n-s","errorCode":null,"errorMessage":"panic in UpdateContexts: %v\\n%s","messagePattern":"panic in UpdateContexts: (.+?)\\\\n(.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"app/server/db/context_helpers_update.go","lineNumber":145,"sourceCode":"\t\treturn nil, fmt.Errorf(\"total context body size exceeds limit (size %.2f MB, limit %d MB)\", float64(totalBodySize)/1024/1024, int(shared.MaxContextBodySize)/1024/1024)\n\t}\n\n\tvar updatedContexts []*shared.Context\n\n\tnumFiles := 0\n\tnumUrls := 0\n\tnumTrees := 0\n\tnumMaps := 0\n\n\tvar mu sync.Mutex\n\terrCh := make(chan error, len(*req))\n\n\tfor id, params := range *req {\n\t\tgo func(id string, params *shared.UpdateContextParams) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tlog.Printf(\"panic in UpdateContexts: %v\\n%s\", r, debug.Stack())\n\t\t\t\t\terrCh <- fmt.Errorf(\"panic in UpdateContexts: %v\\n%s\", r, debug.Stack())\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\t\t\tvar context *Context\n\t\t\tif _, ok := contextsById[id]; ok {\n\t\t\t\tcontext = contextsById[id]\n\t\t\t} else {\n\t\t\t\tvar err error\n\t\t\t\tcontext, err = GetContext(orgId, planId, id, true, true)\n\n\t\t\t\tif err != nil {\n\t\t\t\t\terrCh <- fmt.Errorf(\"error getting context: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// log.Println(\"Got context\", context.Id, \"numTokens\", context.NumTokens)\n\t\t\t}\n\n\t\t\tmu.Lock()","sourceCodeStart":127,"sourceCodeEnd":163,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/context_helpers_update.go#L127-L163","documentation":"UpdateContexts spawns a goroutine per context ID; if that goroutine panics, the deferred recover() logs the panic with a stack trace and forwards a wrapped \"panic in UpdateContexts\" error onto errCh so the caller surfaces it instead of crashing the process. runtime.Goexit() then stops the goroutine to avoid double-sending to the channel.","triggerScenarios":"Any panic inside the per-context update goroutine — e.g. nil-pointer dereference on a context field, out-of-range index, or a callee (token counting, DB layer) panicking on malformed params.Body or unexpected context state.","commonSituations":"Corrupt or nil context rows fetched from the DB; a nil Body or nil map field in UpdateContextParams; library-level panic (e.g. tokenizer) on unusual content; concurrent map access introduced by a code change.","solutions":["Read the logged stack trace from the error message to find the panicking line and fix the underlying nil/invalid-data bug.","Validate UpdateContextParams (non-nil Body, valid fields) before calling UpdateContexts.","Check DB rows for the offending context ID for corruption or missing fields.","Add defensive nil checks in the goroutine's update path upstream of the panic site.","Retry the update after fixing, since errCh delivery fails the whole call."],"exampleFix":"// before\nparams := req[id]\ncontext.NumTokens = countTokens(params.Body) // panics if params or Body nil\n// after\nif params == nil || params.Body == \"\" {\n    errCh <- fmt.Errorf(\"invalid params for context %s\", id)\n    return\n}\ncontext.NumTokens = countTokens(params.Body)","handlingStrategy":"try-catch","validationCode":"for id, p := range req {\n    if p == nil || p.Body == \"\" { return fmt.Errorf(\"nil/empty params for context %s\", id) }\n}\n","typeGuard":"func validUpdateParams(p *shared.UpdateContextParams) bool { return p != nil && p.Body != \"\" }","tryCatchPattern":"_, err := UpdateContexts(orgId, planId, branch, &req)\nif err != nil && strings.HasPrefix(err.Error(), \"panic in UpdateContexts\") {\n    log.Fatalf(\"goroutine panic: %v\", err) // inspect the embedded stack trace\n}","preventionTips":["Nil-check contexts and params inside any goroutine that writes to errCh","Keep the defer/recover + runtime.Goexit pattern to avoid double channel sends","Fuzz token counting/DB paths with malformed bodies to catch panics early","Audit concurrent map access in per-context goroutines"],"tags":["go","panic","recovered","goroutine","concurrency"],"backgroundTag":"recovered-panic","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"}