{"record":{"id":"46732bdb70f141cf","repo":"plandex-ai/plandex","slug":"panic-in-rejectplanfiles-v-n-s","errorCode":null,"errorMessage":"panic in RejectPlanFiles: %v\\n%s","messagePattern":"panic in RejectPlanFiles: (.+?)\\\\n(.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/result_helpers.go","lineNumber":914,"sourceCode":"\tfor i := 0; i < len(files); i++ {\n\t\terr := <-errCh\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error deleting pending results: %v\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc RejectPlanFiles(orgId, planId string, files []string, now time.Time) error {\n\terrCh := make(chan error, len(files))\n\n\tfor _, file := range files {\n\t\tgo func(file string) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tlog.Printf(\"panic in RejectPlanFiles: %v\\n%s\", r, debug.Stack())\n\t\t\t\t\terrCh <- fmt.Errorf(\"panic in RejectPlanFiles: %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\terr := RejectPlanFile(orgId, planId, file, now)\n\n\t\t\tif err != nil {\n\t\t\t\terrCh <- err\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\terrCh <- nil\n\t\t}(file)\n\t}\n\n\tfor i := 0; i < len(files); i++ {\n\t\terr := <-errCh\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error rejecting plan files: %v\", err)","sourceCodeStart":896,"sourceCodeEnd":932,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/result_helpers.go#L896-L932","documentation":"RejectPlanFiles runs RejectPlanFile in a goroutine per file with a recover() guard. If a panic occurs (e.g. nil dereference in RejectPlanFile or GetPlanFileResults), it is converted to 'panic in RejectPlanFiles: %v\\n%s' including the stack trace, sent on errCh, and the goroutine exits via runtime.Goexit.","triggerScenarios":"Any panic inside RejectPlanFile called from this fan-out: nil map/pointer dereference, index out of range, or unexpected nil from helpers like getPlanResultsDir or GetPlanFileResults for a malformed orgId/planId/file.","commonSituations":"Bad inputs (empty orgId/planId, path with weird characters) leading a helper to nil-deref; concurrent map access; library code panicking on unexpected result state.","solutions":["Read the stack trace in the message to locate the panicking function and fix the nil/invalid input","Validate orgId, planId and file paths before calling RejectPlanFiles","Fix the underlying panic source in RejectPlanFile/GetPlanFileResults (nil checks) so the recover guard is a last resort","Convert panics in helpers to returned errors instead of panics"],"exampleFix":"// before\nfunc getPlanResultsDir(orgId, planId string) string {\n    return filepath.Join(cfg.DataDir, orgId, planId, \"results\")\n}\n// after\nfunc getPlanResultsDir(orgId, planId string) (string, error) {\n    if orgId == \"\" || planId == \"\" {\n        return \"\", fmt.Errorf(\"orgId and planId must be non-empty\")\n    }\n    return filepath.Join(cfg.DataDir, orgId, planId, \"results\"), nil\n}","handlingStrategy":"validation","validationCode":"func validateRejectInputs(orgId, planId string, files []string) error {\n    if orgId == \"\" || planId == \"\" { return fmt.Errorf(\"orgId/planId required\") }\n    for _, f := range files {\n        if f == \"\" || strings.ContainsAny(f, \"\\x00\") { return fmt.Errorf(\"invalid file path: %q\", f) }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"err := RejectPlanFiles(orgId, planId, files, time.Now())\nif err != nil && strings.HasPrefix(err.Error(), \"panic in RejectPlanFiles\") {\n    log.Printf(\"panic recovered during reject: %v\", err) // includes stack trace for diagnosis\n}","preventionTips":["Validate orgId/planId/file paths before calling RejectPlanFiles","Keep the recover() + runtime.Goexit guard pattern in all goroutine fan-outs","Write unit tests covering empty/odd inputs to RejectPlanFile to flush out panics","Convert panics in shared helpers into returned errors"],"tags":["go","panic","concurrency","goroutine","recover"],"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"}