{"record":{"id":"d7a8a74512391af0","repo":"plandex-ai/plandex","slug":"error-building-validate-loop-v","errorCode":null,"errorMessage":"error building validate loop: %v","messagePattern":"error building validate loop: (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"app/server/model/plan/build_race.go","lineNumber":231,"sourceCode":"\tonInitialStream := func(chunk string, buffer string) bool {\n\t\tif !startedFallbacks && strings.Contains(buffer, \"<PlandexIncorrect/>\") && strings.Contains(buffer, \"<PlandexComments>\") {\n\t\t\tlog.Printf(\"buildRace - detected incorrect marker, triggering whole file build\")\n\n\t\t\tcomments := utils.GetXMLContent(buffer, \"PlandexComments\")\n\n\t\t\tstartFallbacks(comments)\n\t\t}\n\t\t// keep streaming\n\t\treturn false\n\t}\n\n\tfileState.builderRun.AutoApplyValidationStartedAt = time.Now()\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tlog.Printf(\"panic in buildRace validation loop: %v\\n%s\", r, debug.Stack())\n\t\t\t\tsendErr(fmt.Errorf(\"error building validate loop: %v\", r))\n\t\t\t\truntime.Goexit() // don't allow outer function to continue and double-send to channel\n\t\t\t}\n\t\t}()\n\n\t\tlog.Printf(\"buildRace - starting validation loop\")\n\t\tvalidateResult, err := fileState.buildValidateLoop(buildCtx, buildValidateLoopParams{\n\t\t\toriginalFile:         originalFile,\n\t\t\tupdated:              updated,\n\t\t\tproposedContent:      proposedContent,\n\t\t\tdesc:                 desc,\n\t\t\treasons:              reasons,\n\t\t\tsyntaxErrors:         syntaxErrors,\n\t\t\tinitialPhaseOnStream: onInitialStream,\n\t\t\tisInitial:            true,\n\t\t\tsessionId:            sessionId,\n\t\t})\n\n\t\tfileState.builderRun.AutoApplyValidationFinishedAt = time.Now()","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/model/plan/build_race.go#L213-L249","documentation":"This error wraps a panic recovered from the buildRace validation goroutine. When buildValidateLoop panics (nil dereference, index out of range, etc.), the deferred recover converts the panic value into an error and sends it on the error channel so the racing build can fail gracefully instead of crashing the process; runtime.Goexit then stops the goroutine to prevent a double-send.","triggerScenarios":"A runtime panic inside fileState.buildValidateLoop while it validates/applies proposed edits — e.g. nil map/slice access on proposed content, out-of-range index on file lines, or a nil pointer on a builderRun/fileState field during auto-apply validation.","commonSituations":"Proposed content has unexpected structure (empty file, malformed markers), a tree-sitter parse returns nil nodes that are dereferenced, or concurrent mutation of fileState fields while the validation goroutine reads them.","solutions":["Read the panic stack in the server log ('panic in buildRace validation loop') to find the exact nil/panic site in buildValidateLoop","Add nil/empty checks at the top of buildValidateLoop for proposedContent, updated, and originalFile before processing","Ensure fileState and fileState.builderRun are fully initialized before calling buildRace","Guard concurrent access to fileState fields shared with other build goroutines with a mutex or send immutable copies"],"exampleFix":"// before\nlines := strings.Split(proposedContent, \"\\n\")\nedit := lines[editStart-1]\n// after\nlines := strings.Split(proposedContent, \"\\n\")\nif editStart < 1 || editStart > len(lines) {\n    return buildValidateLoopResult{}, fmt.Errorf(\"edit start %d out of range (file has %d lines)\", editStart, len(lines))\n}\nedit := lines[editStart-1]","handlingStrategy":"try-catch","validationCode":"if proposedContent == \"\" || fileState == nil || fileState.builderRun == nil {\n    return fmt.Errorf(\"precondition failed: empty proposed content or uninitialized fileState\")\n}","typeGuard":"func safeIndex(s string, i int) (byte, bool) {\n    if i < 0 || i >= len(s) { return 0, false }\n    return s[i], true\n}","tryCatchPattern":"// in the goroutine, wrap the whole loop body; the library already does this:\ngo func() {\n    defer func() {\n        if r := recover(); r != nil {\n            log.Printf(\"panic in validation loop: %v\\n%s\", r, debug.Stack())\n            sendErr(fmt.Errorf(\"error building validate loop: %v\", r))\n            runtime.Goexit()\n        }\n    }()\n    // ... buildValidateLoop ...\n}()","preventionTips":["Nil-check all inputs (proposedContent, updated, originalFile) at the entry of buildValidateLoop","Bounds-check line/column indices derived from edit markers before slicing content","Avoid sharing mutable fileState fields across racing goroutines without synchronization","Add panic-recovery + structured logging in every spawned build goroutine"],"tags":["go","panic","goroutine","validation"],"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"}