{"record":{"id":"f486244d8dd54283","repo":"plandex-ai/plandex","slug":"error-starting-fast-apply-v","errorCode":null,"errorMessage":"error starting fast apply: %v","messagePattern":"error starting fast apply: (.+?)","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"app/server/model/plan/build_race.go","lineNumber":127,"sourceCode":"\t\t\t\tsendRes(raceResult{content: content, valid: true})\n\t\t\t}\n\t\t}()\n\t}\n\n\tmaybeStartFastApply := func(onFail func()) {\n\t\tlog.Printf(\"buildRace - starting fast apply\")\n\t\tif !params.didCallFastApply {\n\t\t\tlog.Printf(\"buildRace - fast apply isn't defined, skipping\")\n\t\t\tsendErr(nil) // no error, just no fast apply\n\t\t\tonFail()\n\t\t\treturn\n\t\t}\n\n\t\tgo func() {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\tlog.Printf(\"panic in maybeStartFastApply: %v\\n%s\", r, debug.Stack())\n\t\t\t\t\tsendErr(fmt.Errorf(\"error starting fast apply: %v\", r))\n\t\t\t\t\tonFail()\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\tvar fastApplyRes string\n\n\t\t\tselect {\n\t\t\tcase fastApplyRes = <-fastApplyCh:\n\t\t\tcase <-buildCtx.Done():\n\t\t\t\tlog.Printf(\"buildRace - context canceled, skipping fast apply\")\n\t\t\t\tsendErr(nil) // no error, just no fast apply\n\t\t\t\tonFail()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif fastApplyRes == \"\" {\n\t\t\t\tlog.Printf(\"buildRace - fast apply isn't defined or failed to run\")\n\t\t\t\tsendErr(nil) // no error, just no fast apply","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/plandex-ai/plandex/blob/e2d772072efadbe41d2946d97d79be55532dbab5/app/server/model/plan/build_race.go#L109-L145","documentation":"maybeStartFastApply's goroutine recovered from a panic (recover() != nil) and reports it as 'error starting fast apply'. It converts an unexpected runtime panic during the fast-apply race leg into a regular error sent to errCh, calls onFail to start the whole-file build, and calls runtime.Goexit to prevent double-sending on the channel.","triggerScenarios":"Any panic inside the fast-apply goroutine: e.g. nil map/slice access when reading fastApplyCh results, nil pointer on fileState.builderRun, index-out-of-range while processing the fast apply result, or a panicking library call before/around validateSyntax.","commonSituations":"Refactors that leave fileState fields nil when fast apply is invoked concurrently; third-party syntax-parsing libraries panicking on malformed input; data races writing shared state between the race goroutines.","solutions":["Read the debug.Stack() output logged as 'panic in maybeStartFastApply' to find the panicking frame","Guard nil fileState/builderRun fields before entering the goroutine","Add recover-safe wrappers around third-party syntax parsing calls","Run with -race to detect data races between fast-apply and validation goroutines"],"exampleFix":"// before\nfastApplyRes = <-fastApplyCh // panic if channel closed or state nil\n// after\nres, ok := <-fastApplyCh\nif !ok || fileState == nil || fileState.builderRun == nil {\n\tsendErr(nil)\n\tonFail()\n\treturn\n}\nfastApplyRes = res","handlingStrategy":"type-guard","validationCode":"if fileState == nil || fileState.builderRun == nil {\n\tlog.Printf(\"skipping fast apply: uninitialized state\")\n\tsendErr(nil)\n\tonFail()\n\treturn\n}\nif fastApplyCh == nil {\n\tsendErr(nil)\n\tonFail()\n\treturn\n}","typeGuard":"func fastApplyStateReady(fs *activeBuildStreamFileState, ch chan string) bool {\n\treturn fs != nil && fs.builderRun != nil && ch != nil\n}","tryCatchPattern":"go func() {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tlog.Printf(\"panic in maybeStartFastApply: %v\\n%s\", r, debug.Stack())\n\t\t\tsendErr(fmt.Errorf(\"error starting fast apply: %v\", r))\n\t\t\tonFail()\n\t\t\truntime.Goexit()\n\t\t}\n\t}()\n\t// fast-apply work\n}()","preventionTips":["Nil-check fileState and builderRun before spawning the goroutine","Guard channel receives with the comma-ok form to detect closed channels","Run tests with -race to catch data races on shared build state","Wrap third-party parsing calls so their panics don't propagate"],"tags":["go","panic","concurrency","recovery"],"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"}