plandex-ai/plandex · error
panic in GetPlanDiffs: %v\n%s
Error message
panic in GetPlanDiffs: %v\n%s
What it means
Each per-path goroutine inside GetPlanDiffs has a deferred recover() so a panic while processing one file context does not crash the server. Instead the panic value and stack trace are logged, converted to an error, and sent on errCh; runtime.Goexit() stops the goroutine so nothing else is sent. This error surfaces through the outer loop as 'error writing original files to temp dir: panic in GetPlanDiffs: ...'.
Source
Thrown at app/server/db/diff_helpers.go:55
err = initGitRepo(tempDirPath)
if err != nil {
return "", fmt.Errorf("error initializing git repo: %v", err)
}
files := planState.CurrentPlanFiles.Files
removed := planState.CurrentPlanFiles.Removed
// write the original files to the temp dir
errCh := make(chan error, len(planState.ContextsByPath))
hasAnyOriginal := false
for path, context := range planState.ContextsByPath {
go func(path string, context *shared.Context) {
defer func() {
if r := recover(); r != nil {
log.Printf("panic in GetPlanDiffs: %v\n%s", r, debug.Stack())
errCh <- fmt.Errorf("panic in GetPlanDiffs: %v\n%s", r, debug.Stack())
runtime.Goexit() // don't allow outer function to continue and double-send to channel
}
}()
_, hasPath := files[path]
_, hasRemoved := removed[path]
if hasPath || hasRemoved {
hasAnyOriginal = true
// ensure file directory exists
err = os.MkdirAll(filepath.Dir(filepath.Join(tempDirPath, path)), 0755)
if err != nil {
errCh <- fmt.Errorf("error creating directory: %v", err)
return
}
err = os.WriteFile(filepath.Join(tempDirPath, path), []byte(context.Body), 0644)
if err != nil {
errCh <- fmt.Errorf("error writing file: %v", err)
returnView on GitHub (pinned to e2d772072e)
Solutions
- Read the stack trace embedded in the message to find the nil/invalid value; inspect the offending path's context in the plan state.
- Harden the goroutine body: nil-check context and context.Body before use, and validate planState maps.
- If plan state is corrupted, repair or re-load it via GetCurrentPlanState or restore from backup.
- Check for data races on planState/files/removed shared with the goroutines and add synchronization or copies.
Example fix
// before
_, hasPath := files[path]
// after (nil-safe)
if context == nil {
errCh <- fmt.Errorf("nil context for path %s", path)
return
}
_, hasPath := files[path] Defensive patterns
Strategy: try-catch
Validate before calling
for path, ctx := range planState.ContextsByPath {
if ctx == nil {
return fmt.Errorf("nil context for path %q in plan state", path)
}
} Type guard
func isValidContext(c *shared.Context) bool {
return c != nil
} Try / catch
// already guarded in-library; at the call site:
out, err := GetPlanDiffs(orgId, planId, plain)
if err != nil && strings.HasPrefix(err.Error(), "error writing original files to temp dir: panic in GetPlanDiffs") {
log.Printf("worker panic while diffing plan %s: %v", planId, err)
// treat plan state as suspect: reload or surface to user
} Prevention
- Nil-check context pointers before spawning worker goroutines.
- Never share/mutate planState maps while goroutines read them; snapshot or synchronize.
- Keep the recover()+Goexit pattern intact in any new goroutine code so panics become errors.
- Add tests covering plan state with missing/nil contexts.
When it happens
Trigger: A per-path worker goroutine panics (e.g. nil pointer dereference if a *shared.Context is nil, or an out-of-range/nil map access) while building diffs for planState.ContextsByPath.
Common situations: Plan state contains a context entry whose Body or pointer is nil due to corrupted/incomplete persisted state; concurrent mutation of planState while goroutines read it; a regression in the loop body code.
Related errors
- panic in UpdateContexts: %v\n%s
- panic in GetPlanConvo: %v\n%s
- panic in gitRemoveIndexLockFileIfExists: %v %s
- panic in DeleteDraftPlans: %v %s
- panic in GetFullCurrentPlanStateParams: %v\n%s
AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05).
Data as JSON: /api/errors/234463fb2e6c4e1b.
Report an issue: GitHub.