plandex-ai/plandex · error

error unmarshalling convo file: %v

Error message

error unmarshalling convo file: %v

What it means

A convo file was read successfully but json.Unmarshal could not decode it into the ConvoMessage struct. The invalid file is reported through errCh and the whole GetPlanConvo call fails wrapped as 'error reading convo files'. This is a data-integrity error: the stored JSON does not match the current ConvoMessage schema.

Source

Thrown at app/server/db/convo_helpers.go:58

			defer func() {
				if r := recover(); r != nil {
					log.Printf("panic in GetPlanConvo: %v\n%s", r, debug.Stack())
					errCh <- fmt.Errorf("panic in GetPlanConvo: %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(convoDir, file.Name()))

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

			var convoMessage ConvoMessage
			err = json.Unmarshal(bytes, &convoMessage)

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

			convoCh <- &convoMessage

		}(file)
	}

	for i := 0; i < len(files); i++ {
		select {
		case err := <-errCh:
			return nil, fmt.Errorf("error reading convo files: %v", err)
		case convoMessage := <-convoCh:
			convo = append(convo, convoMessage)
		}
	}

	sort.Slice(convo, func(i, j int) bool {

View on GitHub (pinned to e2d772072e)

Solutions

  1. Identify the corrupt file (wrapped underlying error names the JSON offset/type) and delete or repair it
  2. Validate the file with 'jq . <file>.json' to see the exact JSON syntax problem
  3. Restore the file from backup if it was truncated
  4. Run any available Plandex data-migration for plans after a version upgrade
  5. Add schema-defaulting (e.g. disallowunknownfields off, per-field pointers) if upgrading across schema changes

Example fix

// inspect corrupt file
// before
json.Unmarshal(bytes, &convoMessage)
// after
if err := json.Unmarshal(bytes, &convoMessage); err != nil {
    log.Printf("corrupt convo file %s: %v", file.Name(), err)
}
if err := json.Unmarshal(bytes, &convoMessage); err != nil {
    errCh <- fmt.Errorf("error unmarshalling convo file: %v", err)
}
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(path)
if err == nil && info.Size() == 0 {
    return fmt.Errorf("truncated convo file: %s", path)
}
var probe map[string]any
if data, err := os.ReadFile(path); err == nil {
    if err := json.Unmarshal(data, &probe); err != nil {
        return fmt.Errorf("corrupt convo file %s: %w", path, err)
    }
}

Type guard

func isConvoMessage(b []byte) bool {
    var m db.ConvoMessage
    return json.Unmarshal(b, &m) == nil && m.Id != ""
}

Try / catch

convo, err := db.GetPlanConvo(orgId, planId)
if err != nil {
    if strings.Contains(err.Error(), "unmarshalling convo file") {
        // locate corrupt file, back it up, remove it, retry
        quarantineCorruptConvoFiles(convoDir)
        convo, err = db.GetPlanConvo(orgId, planId)
    }
    return err
}

Prevention

When it happens

Trigger: os.ReadFile succeeds but the bytes are not valid JSON matching ConvoMessage: zero-length/truncated file from an interrupted write, manually edited file, schema change after a version upgrade (field type changed), or a non-JSON file (editor swap file, .DS_Store-style artifact) dropped into the convo dir.

Common situations: Server crash or power loss during StoreConvoMessage leaving a partial JSON file; downgrading/upgrading Plandex so old or new field types no longer unmarshal; operator editing message JSON by hand; backup/restore tooling corrupting files.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


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