plandex-ai/plandex · error

panic in DeletePendingResultsForPaths: %v\n%s

Error message

panic in DeletePendingResultsForPaths: %v\n%s

What it means

Like RejectAllResults, DeletePendingResultsForPaths spawns one goroutine per result file with a recover() defer. A panic in the goroutine is logged and converted into this error message with a stack trace, sent on errCh, and the goroutine exits via runtime.Goexit(). It means reading/deleting a pending result panicked.

Source

Thrown at app/server/db/result_helpers.go:860

	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}

		return fmt.Errorf("error reading results dir: %v", err)
	}

	errCh := make(chan error, len(files))

	for _, file := range files {
		resultId := strings.TrimSuffix(file.Name(), ".json")

		go func(resultId string) {
			defer func() {
				if r := recover(); r != nil {
					log.Printf("panic in DeletePendingResultsForPaths: %v\n%s", r, debug.Stack())
					errCh <- fmt.Errorf("panic in DeletePendingResultsForPaths: %v\n%s", r, debug.Stack())
					runtime.Goexit() // don't allow outer function to continue and double-send to channel
				}
			}()
			bytes, err := os.ReadFile(filepath.Join(resultsDir, resultId+".json"))

			if err != nil {
				errCh <- fmt.Errorf("error reading result file: %v", err)
				return
			}

			var result PlanFileResult
			err = json.Unmarshal(bytes, &result)

			if err != nil {
				errCh <- fmt.Errorf("error unmarshalling result file: %v", err)
				return
			}

View on GitHub (pinned to e2d772072e)

Solutions

  1. Use the logged stack trace (embedded in the error string) to find the panicking line
  2. Guard nil fields in PlanFileResult/ToApi/IsPending before dereferencing
  3. Migrate or remove legacy-schema result files that break the current unmarshal expectations
  4. Test DeletePendingResultsForPaths against corrupt and minimal result fixtures

Example fix

// before
if result.ToApi().IsPending() && paths[result.Path] {
// after
toApi := result.ToApi()
if toApi != nil && toApi.IsPending() && paths[toApi.Path] {
Defensive patterns

Strategy: type-guard

Validate before calling

var result PlanFileResult
if err := json.Unmarshal(bytes, &result); err != nil {
    errCh <- fmt.Errorf("skipping corrupt result %s: %v", resultId, err)
    return
}
toApi := result.ToApi()
if toApi == nil {
    errCh <- nil
    return
}

Type guard

func validPlanResult(r *PlanFileResultApi) bool {
    return r != nil && r.Path != ""
}

Try / catch

go func(resultId string) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("panic in DeletePendingResultsForPaths: %v\n%s", r, debug.Stack())
            errCh <- fmt.Errorf("panic in DeletePendingResultsForPaths: %v\n%s", r, debug.Stack())
            runtime.Goexit()
        }
    }()
    _ = processResult(resultId)
}(resultId)

Prevention

When it happens

Trigger: Nil dereference or unexpected type inside the goroutine — e.g. result.ToApi() returning something dereferenced unsafely in IsPending, malformed state after unmarshal, or a race on shared data while checking paths[result.Path].

Common situations: Result JSON missing required fields after manual edits or version skew (older schema files read by newer code); data races between concurrent apply/delete jobs on the same plan; bugs in PlanFileResult.ToApi exposed by edge-case files.

Related errors


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/ddc9ce8f2fd9f539. Report an issue: GitHub.