plandex-ai/plandex · error
error starting fast apply: %v
Error message
error starting fast apply: %v
What it means
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.
Source
Thrown at app/server/model/plan/build_race.go:127
sendRes(raceResult{content: content, valid: true})
}
}()
}
maybeStartFastApply := func(onFail func()) {
log.Printf("buildRace - starting fast apply")
if !params.didCallFastApply {
log.Printf("buildRace - fast apply isn't defined, skipping")
sendErr(nil) // no error, just no fast apply
onFail()
return
}
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in maybeStartFastApply: %v\n%s", r, debug.Stack())
sendErr(fmt.Errorf("error starting fast apply: %v", r))
onFail()
runtime.Goexit() // don't allow outer function to continue and double-send to channel
}
}()
var fastApplyRes string
select {
case fastApplyRes = <-fastApplyCh:
case <-buildCtx.Done():
log.Printf("buildRace - context canceled, skipping fast apply")
sendErr(nil) // no error, just no fast apply
onFail()
return
}
if fastApplyRes == "" {
log.Printf("buildRace - fast apply isn't defined or failed to run")
sendErr(nil) // no error, just no fast applyView on GitHub (pinned to e2d772072e)
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
Example fix
// before
fastApplyRes = <-fastApplyCh // panic if channel closed or state nil
// after
res, ok := <-fastApplyCh
if !ok || fileState == nil || fileState.builderRun == nil {
sendErr(nil)
onFail()
return
}
fastApplyRes = res Defensive patterns
Strategy: type-guard
Validate before calling
if fileState == nil || fileState.builderRun == nil {
log.Printf("skipping fast apply: uninitialized state")
sendErr(nil)
onFail()
return
}
if fastApplyCh == nil {
sendErr(nil)
onFail()
return
} Type guard
func fastApplyStateReady(fs *activeBuildStreamFileState, ch chan string) bool {
return fs != nil && fs.builderRun != nil && ch != nil
} Try / catch
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in maybeStartFastApply: %v\n%s", r, debug.Stack())
sendErr(fmt.Errorf("error starting fast apply: %v", r))
onFail()
runtime.Goexit()
}
}()
// fast-apply work
}() Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- panic in UpdateContexts: %v\n%s
- panic in GetPlanConvo: %v\n%s
- panic in gitRemoveIndexLockFileIfExists: %v %s
- panic in SyncPlanTokens: %v %s
- panic in DeleteDraftPlans: %v %s
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/f486244d8dd54283.
Report an issue: GitHub.