plandex-ai/plandex · critical
panic in UpdateContexts: %v\n%s
Error message
panic in UpdateContexts: %v\n%s
What it means
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.
Source
Thrown at app/server/db/context_helpers_update.go:145
return nil, fmt.Errorf("total context body size exceeds limit (size %.2f MB, limit %d MB)", float64(totalBodySize)/1024/1024, int(shared.MaxContextBodySize)/1024/1024)
}
var updatedContexts []*shared.Context
numFiles := 0
numUrls := 0
numTrees := 0
numMaps := 0
var mu sync.Mutex
errCh := make(chan error, len(*req))
for id, params := range *req {
go func(id string, params *shared.UpdateContextParams) {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in UpdateContexts: %v\n%s", r, debug.Stack())
errCh <- fmt.Errorf("panic in UpdateContexts: %v\n%s", r, debug.Stack())
runtime.Goexit() // don't allow outer function to continue and double-send to channel
}
}()
var context *Context
if _, ok := contextsById[id]; ok {
context = contextsById[id]
} else {
var err error
context, err = GetContext(orgId, planId, id, true, true)
if err != nil {
errCh <- fmt.Errorf("error getting context: %v", err)
return
}
// log.Println("Got context", context.Id, "numTokens", context.NumTokens)
}
mu.Lock()View on GitHub (pinned to e2d772072e)
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.
Example fix
// before
params := req[id]
context.NumTokens = countTokens(params.Body) // panics if params or Body nil
// after
if params == nil || params.Body == "" {
errCh <- fmt.Errorf("invalid params for context %s", id)
return
}
context.NumTokens = countTokens(params.Body) Defensive patterns
Strategy: try-catch
Validate before calling
for id, p := range req {
if p == nil || p.Body == "" { return fmt.Errorf("nil/empty params for context %s", id) }
}
Type guard
func validUpdateParams(p *shared.UpdateContextParams) bool { return p != nil && p.Body != "" } Try / catch
_, err := UpdateContexts(orgId, planId, branch, &req)
if err != nil && strings.HasPrefix(err.Error(), "panic in UpdateContexts") {
log.Fatalf("goroutine panic: %v", err) // inspect the embedded stack trace
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- panic in GetPlanConvo: %v\n%s
- panic in gitRemoveIndexLockFileIfExists: %v %s
- panic in DeleteDraftPlans: %v %s
- panic in GetFullCurrentPlanStateParams: %v\n%s
- panic in GetCurrentPlanState: %v\n%s
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/3fd1508b6c4b3761.
Report an issue: GitHub.