{"record":{"id":"fabdad2dff4f43e3","repo":"plandex-ai/plandex","slug":"panic-in-getplanfileresults-v-n-s","errorCode":null,"errorMessage":"panic in GetPlanFileResults: %v\\n%s","messagePattern":"panic in GetPlanFileResults: (.+?)\\\\n(.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/server/db/result_helpers.go","lineNumber":403,"sourceCode":"\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn results, nil\n\t\t}\n\n\t\treturn nil, fmt.Errorf(\"error reading results dir: %v\", err)\n\t}\n\n\terrCh := make(chan error, len(files))\n\tresultCh := make(chan *PlanFileResult, len(files))\n\n\tfor _, file := range files {\n\t\t// log.Printf(\"Result file: %s\", file.Name())\n\n\t\tgo func(file os.DirEntry) {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tlog.Printf(\"panic in GetPlanFileResults: %v\\n%s\", r, debug.Stack())\n\t\t\t\t\terrCh <- fmt.Errorf(\"panic in GetPlanFileResults: %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\n\t\t\tbytes, err := os.ReadFile(filepath.Join(resultsDir, file.Name()))\n\n\t\t\tif err != nil {\n\t\t\t\terrCh <- fmt.Errorf(\"error reading result file: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tvar result PlanFileResult\n\t\t\terr = json.Unmarshal(bytes, &result)\n\n\t\t\tif err != nil {\n\t\t\t\terrCh <- fmt.Errorf(\"error unmarshalling result file: %v\", err)\n\t\t\t\treturn\n\t\t\t}","sourceCodeStart":385,"sourceCodeEnd":421,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/db/result_helpers.go#L385-L421","documentation":"Each per-file goroutine in GetPlanFileResults has a recover() guard; if the file processing panics (e.g. nil deref, index out of range), the panic is converted into this error carrying the value and a full debug.Stack(), sent on errCh, and the goroutine exits via runtime.Goexit to avoid a double channel send.","triggerScenarios":"Any panic inside the per-file goroutine body: unexpected nil pointers while populating PlanFileResult, slicing errors, or bugs triggered by malformed but parseable JSON content.","commonSituations":"A result file with valid JSON but unexpected structure (nulls where structs are expected) tripping downstream code; concurrent map access bugs.","solutions":["Read the appended debug.Stack() to find the panicking line","Harden the parsing/population code against nil/zero-value fields","Write a regression test with the offending result file","Keep the recover/Goexit guard — it prevents the panic from crashing the whole process"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"results, err := db.GetPlanFileResults(orgId, planId)\nif err != nil {\n    if strings.Contains(err.Error(), \"panic in GetPlanFileResults\") {\n        // stack is embedded in err; report to maintainers with the file contents\n    }\n    return err\n}","preventionTips":["Keep result files schema-consistent with PlanFileResult","Report panics with the embedded stack trace verbatim","Run regression tests against real-world result payloads"],"tags":["go","panic","concurrency","stack-trace"],"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"}