{"record":{"id":"f9047c5a39eb90db","repo":"plandex-ai/plandex","slug":"error-storing-user-message-v","errorCode":null,"errorMessage":"error storing user message: %v","messagePattern":"error storing user message: (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"app/server/model/plan/tell_load.go","lineNumber":198,"sourceCode":"\t\t\tres, err := db.GetPlanConvo(currentOrgId, planId)\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"Error getting plan convo: %v\\n\", err)\n\t\t\t\terrCh <- fmt.Errorf(\"error getting plan convo: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconvo = res\n\t\t\tUpdateActivePlan(planId, branch, func(ap *types.ActivePlan) {\n\t\t\t\tap.MessageNum = len(convo)\n\t\t\t})\n\n\t\t\tpromptTokens := shared.GetNumTokensEstimate(req.Prompt)\n\t\t\tinnerErrCh := make(chan error, 2)\n\n\t\t\tgo func() {\n\t\t\t\tdefer func() {\n\t\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\t\tlog.Printf(\"panic in storeUserMessage: %v\\n%s\", r, debug.Stack())\n\t\t\t\t\t\tinnerErrCh <- fmt.Errorf(\"error storing user message: %v\", r)\n\t\t\t\t\t\truntime.Goexit() // don't allow outer function to continue and double-send to channel\n\t\t\t\t\t}\n\t\t\t\t}()\n\n\t\t\t\tif iteration == 0 && missingFileResponse == \"\" && !req.IsUserContinue {\n\t\t\t\t\tnum := len(convo) + 1\n\n\t\t\t\t\tlog.Printf(\"[TellLoad] storing user message | len(convo): %d | num: %d\\n\", len(convo), num)\n\n\t\t\t\t\tpromptMsg = &db.ConvoMessage{\n\t\t\t\t\t\tOrgId:   currentOrgId,\n\t\t\t\t\t\tPlanId:  planId,\n\t\t\t\t\t\tUserId:  currentUserId,\n\t\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\t\tTokens:  promptTokens,\n\t\t\t\t\t\tNum:     num,\n\t\t\t\t\t\tMessage: req.Prompt,\n\t\t\t\t\t\tFlags: shared.ConvoMessageFlags{","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/model/plan/tell_load.go#L180-L216","documentation":"This error is produced by the storeUserMessage goroutine's recover handler: the goroutine that persists the user's prompt message panicked. The panic value is wrapped and sent on innerErrCh so loadTellPlan aborts instead of crashing the process.","triggerScenarios":"A panic inside the goroutine guarded at tell_load.go:198 — e.g., nil repo pointer, nil promptMsg construction failure, nil map access while building the db.ConvoMessage, or a panic inside db.StoreConvoMessage or UpdateActivePlan during the first iteration (iteration==0, no missingFileResponse, not IsUserContinue).","commonSituations":"Git repo wrapper (repo) is nil because the repository failed to load or was closed concurrently; active plan record was deleted mid-request causing nil deref in UpdateActivePlan; nil auth.User or currentUserId from a stale session.","solutions":["Inspect the 'panic in storeUserMessage' log stack trace to pinpoint the deref site","Ensure the repo (git repo wrapper) is successfully opened and non-nil before starting the goroutine","Validate auth.User and currentUserId are set before calling loadTellPlan","Guard UpdateActivePlan against a missing active plan entry for planId/branch","Run with -race to detect concurrent access to the active plan or repo"],"exampleFix":"// before\n_, err = db.StoreConvoMessage(repo, promptMsg, auth.User.Id, branch, true)\n// after\nif repo == nil || auth == nil || auth.User == nil {\n    innerErrCh <- fmt.Errorf(\"error storing user message: nil repo or user\")\n    return\n}\n_, err = db.StoreConvoMessage(repo, promptMsg, auth.User.Id, branch, true)","handlingStrategy":"validation","validationCode":"if repo == nil {\n    return fmt.Errorf(\"git repo not initialized; cannot store user message\")\n}\nif auth == nil || auth.User == nil || auth.User.Id == \"\" {\n    return fmt.Errorf(\"authenticated user required to store user message\")\n}","typeGuard":"func canStoreUserMessage(repo *git.Repository, auth *auth.Session) bool {\n    return repo != nil && auth != nil && auth.User != nil && auth.User.Id != \"\"\n}","tryCatchPattern":"if err := <-innerErrCh; err != nil && strings.HasPrefix(err.Error(), \"error storing user message:\") {\n    // recovered panic path — inspect stack trace already logged, abort plan load\n    return err\n}","preventionTips":["Ensure the git repo wrapper is opened and kept alive for the request lifetime","Check active plan exists for planId/branch before UpdateActivePlan mutations","Validate session/user context before Tell entry point","Enable -race in CI to catch concurrent repo/plan access"],"tags":["panic","goroutine","recovered-panic","database"],"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-14T00:17:10.932Z"}