plandex-ai/plandex · critical

panic in GetConvoMessageDescriptions: %v\n%s

Error message

panic in GetConvoMessageDescriptions: %v\n%s

What it means

GetConvoMessageDescriptions spawns one goroutine per description file, each guarded by a deferred recover(). If reading or parsing a file panics (nil deref, out-of-range, etc.), the recover converts the panic value plus debug.Stack() into 'panic in GetConvoMessageDescriptions: %v\n%s', sends it on errCh, and calls runtime.Goexit to stop the goroutine without double-sending.

Source

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

	if err != nil {

		if os.IsNotExist(err) {
			return descriptions, nil
		}

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

	errCh := make(chan error, len(files))
	descCh := make(chan *ConvoMessageDescription, len(files))

	for _, file := range files {
		go func(file os.DirEntry) {
			defer func() {
				if r := recover(); r != nil {
					log.Printf("panic in GetConvoMessageDescriptions: %v\n%s", r, debug.Stack())
					errCh <- fmt.Errorf("panic in GetConvoMessageDescriptions: %v\n%s", r, debug.Stack())
					runtime.Goexit() // don't allow outer function to continue and double-send to channel
				}
			}()
			path := filepath.Join(descriptionsDir, file.Name())

			bytes, err := os.ReadFile(path)

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

			var description ConvoMessageDescription
			err = json.Unmarshal(bytes, &description)

			if err != nil {
				log.Println("Error unmarshalling description file:", path)
				log.Println("bytes:")

View on GitHub (pinned to e2d772072e)

Solutions

  1. Extract the stack trace from the error message and locate the panicking line in the per-file goroutine
  2. Check the server log for the identical log.Printf trace for goroutine context
  3. Identify the specific description file involved and inspect/repair or remove it
  4. Ensure ConvoMessageDescription (and any custom UnmarshalJSON) handles nil/empty input without panicking
  5. Consider bounding per-file goroutine concurrency with a worker pool if memory pressure triggers panics

Example fix

// before
bytes, err := os.ReadFile(path)
if err != nil {
    errCh <- fmt.Errorf("error reading description file %s: %v", file.Name(), err)
    return
}
// after
if !file.Type().IsRegular() {
    descCh <- nil // skip non-regular entries like sockets/dirs
    return
}
bytes, err := os.ReadFile(path)
if err != nil {
    errCh <- fmt.Errorf("error reading description file %s: %v", file.Name(), err)
    return
}
Defensive patterns

Strategy: try-catch

Validate before calling

// list and pre-screen entries before triggering per-file goroutines
entries, err := os.ReadDir(getPlanDescriptionsDir(orgId, planId))
if err == nil {
    for _, e := range entries {
        if !e.Type().IsRegular() {
            log.Printf("skipping non-regular description entry: %s", e.Name())
        }
    }
}

Type guard

func isDescParsePanicError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "panic in GetConvoMessageDescriptions")
}

Try / catch

descriptions, err := GetConvoMessageDescriptions(orgId, planId)
if err != nil {
    if isDescParsePanicError(err) {
        log.Printf("per-file goroutine panic, stack embedded:\n%s", err)
        return nil, err
    }
    return nil, err
}

Prevention

When it happens

Trigger: A panic occurs inside a per-file goroutine (result_helpers.go:327-357) — e.g. during os.ReadFile, json.Unmarshal into ConvoMessageDescription, or while handling a file whose Name() or path is unexpected (special files, entries vanished mid-iteration).

Common situations: Race between ReadDir and file deletion (file removed between listing and ReadFile in exotic setups); extremely large or zero-length files causing unexpected library panics; custom UnmarshalJSON panicking on malformed data; memory pressure under very large plans with thousands of concurrent per-file goroutines.

Related errors


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