{"record":{"id":"3445ac975da0df86","repo":"plandex-ai/plandex","slug":"error-storing-plan-result-planres-is-nil","errorCode":null,"errorMessage":"Error storing plan result: planRes is nil","messagePattern":"Error storing plan result: planRes is nil","errorType":"exception","errorClass":null,"httpStatus":500,"severity":"error","filePath":"app/server/model/plan/build_finish.go","lineNumber":188,"sourceCode":"\tactiveBuild := fileState.activeBuild\n\n\tactivePlan := GetActivePlan(planId, branch)\n\n\tif activePlan == nil {\n\t\tlog.Println(\"onFinishBuildFile - Active plan not found\")\n\t\treturn\n\t}\n\n\tfilePath := fileState.filePath\n\n\tlog.Printf(\"onFinishBuildFile: %s\\n\", filePath)\n\n\tif planRes == nil {\n\t\tlog.Println(\"onFinishBuildFile - planRes is nil\")\n\t\tgo notify.NotifyErr(notify.SeverityError, fmt.Errorf(\"onFinishBuildFile: planRes is nil\"))\n\n\t\tactivePlan.StreamDoneCh <- &shared.ApiError{\n\t\t\tType:   shared.ApiErrorTypeOther,\n\t\t\tStatus: http.StatusInternalServerError,\n\t\t\tMsg:    \"Error storing plan result: planRes is nil\",\n\t\t}\n\t\treturn\n\t}\n\n\terr := db.ExecRepoOperation(db.ExecRepoOperationParams{\n\t\tOrgId:       currentOrgId,\n\t\tUserId:      fileState.currentUserId,\n\t\tPlanId:      planId,\n\t\tBranch:      branch,\n\t\tPlanBuildId: build.Id,\n\t\tScope:       db.LockScopeWrite,\n\t\tCtx:         activePlan.Ctx,\n\t\tCancelFn:    activePlan.CancelFn,\n\t\tReason:      \"store plan result\",\n\t}, func(repo *db.GitRepo) error {\n\t\tlog.Println(\"Storing plan result\", planRes.Path)","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/model/plan/build_finish.go#L170-L206","documentation":"onFinishBuildFile is the terminal callback for a single-file plan build and expects a non-nil *db.PlanFileResult holding the LLM's constructed file content. When it is called with planRes == nil, Plandex sends \"Error storing plan result: planRes is nil\" as a 500 ApiError on StreamDoneCh and reports via notify. This is an internal invariant violation: some code path invoked the finish callback without a produced result.","triggerScenarios":"buildFile or buildStructuredEdits reaching onFinishBuildFile with a nil planRes — e.g. the model stream ended without yielding a file result, an upstream code path forgot to assign the result after parsing the response, or a fallback/error path calls the finish handler directly without constructing a PlanFileResult.","commonSituations":"Model API responses that come back truncated or empty so parsing produces no result; new/untested plan modes (e.g. structured edits) that return nil on certain operation shapes; custom forks where the streaming callback was modified; races where the stream was canceled before the result was captured.","solutions":["Inspect server logs for the preceding \"onFinishBuildFile - planRes is nil\" line and the model stream outcome to find which caller produced the nil result","Check whether the model response for that file was empty/truncated (API errors, token limits) and retry the plan operation","Audit the calling path (buildFile / buildStructuredEdits) to ensure every branch constructs a *db.PlanFileResult before calling onFinishBuildFile","Ensure the plan's configured model is returning valid file content (switch model or reduce file size if responses are cut off)"],"exampleFix":"// before\nres, err := parsePlanFileResult(streamRes)\nif err != nil {\n\treturn err\n}\nstate.onFinishBuildFile(res)\n// after\nres, err := parsePlanFileResult(streamRes)\nif err != nil {\n\treturn err\n}\nif res == nil {\n\treturn fmt.Errorf(\"no plan file result produced for %s\", filePath)\n}\nstate.onFinishBuildFile(res)","handlingStrategy":"type-guard","validationCode":"// guard before invoking the finish callback\nif planRes == nil {\n\treturn fmt.Errorf(\"buildFile(%s): model stream ended without a file result\", filePath)\n}","typeGuard":"func isValidPlanFileResult(res *db.PlanFileResult) bool {\n\treturn res != nil && res.Path != \"\"\n}\n\n// at the call site:\nif !isValidPlanFileResult(planRes) {\n\t// route to error handler instead of onFinishBuildFile\n}","tryCatchPattern":"// in Go there is no try/catch; guard the callback input\nif planRes == nil {\n\tfileState.onBuildFileError(fmt.Errorf(\"planRes is nil for %s\", filePath))\n\treturn\n}","preventionTips":["Never call onFinishBuildFile directly with a result that came from a parse function without a nil check","Route all build failures through onBuildFileError instead of silently skipping result construction","Add tests for empty/truncated model responses in buildFile and buildStructuredEdits","Log model stream completion status before invoking finish callbacks"],"tags":["plandex","server","nil-pointer","internal-state","plan-build"],"backgroundTag":"nil-plan-result","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"}