plandex-ai/plandex · error

error unmarshalling convo message: %v

Error message

error unmarshalling convo message: %v

What it means

GetConvoMessage read the message file but json.Unmarshal could not decode its bytes into a ConvoMessage. This is a data-integrity error indicating the stored JSON is invalid or its schema no longer matches the ConvoMessage struct expected by the running server version.

Source

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

	})

	return convo, nil
}

func GetConvoMessage(orgId, planId, messageId string) (*ConvoMessage, error) {
	convoDir := getPlanConversationDir(orgId, planId)

	filePath := filepath.Join(convoDir, messageId+".json")

	bytes, err := os.ReadFile(filePath)
	if err != nil {
		return nil, fmt.Errorf("error reading convo message: %v", err)
	}

	var convoMessage ConvoMessage
	err = json.Unmarshal(bytes, &convoMessage)
	if err != nil {
		return nil, fmt.Errorf("error unmarshalling convo message: %v", err)
	}

	return &convoMessage, nil
}

func StoreConvoMessage(repo *GitRepo, message *ConvoMessage, currentUserId, branch string, commit bool) (string, error) {
	convoDir := getPlanConversationDir(message.OrgId, message.PlanId)

	ts := time.Now().UTC()

	if message.Id == "" {
		message.Id = uuid.New().String()
	}

	message.CreatedAt = ts

	bytes, err := json.Marshal(message)

View on GitHub (pinned to e2d772072e)

Solutions

  1. Validate the file with 'jq . <convoDir>/<messageId>.json' to pinpoint the JSON error and offset
  2. Repair or restore the message file from backup
  3. Delete the corrupt message file if it can be regenerated (partial replies are often re-storable via StorePartialReply)
  4. Run Plandex's data migration after version upgrades before serving plans
  5. Pin matching client/server versions so stored and expected schemas agree
Defensive patterns

Strategy: validation

Validate before calling

path := filepath.Join(getPlanConversationDir(orgId, planId), messageId+".json")
if data, err := os.ReadFile(path); err == nil {
    var probe db.ConvoMessage
    if err := json.Unmarshal(data, &probe); err != nil {
        return fmt.Errorf("message %s is corrupt: %w", messageId, err)
    }
    if probe.Id == "" {
        return fmt.Errorf("message %s missing required fields", messageId)
    }
}

Type guard

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

Try / catch

msg, err := db.GetConvoMessage(orgId, planId, messageId)
if err != nil {
    if strings.Contains(err.Error(), "unmarshalling convo message") {
        backupAndQuarantine(path) // then surface as 422/500 data-corruption
    }
    return err
}

Prevention

When it happens

Trigger: The message file is truncated/zero-length (interrupted write), manually edited and syntactically invalid, or was written by a different Plandex version whose field types changed (e.g. string vs number, changed object shape) so unmarshal fails on type mismatch.

Common situations: Server crash mid-write in StoreConvoMessage; Plandex upgrade/downgrade across a ConvoMessage schema change; operator hand-editing message JSON; corrupted backup restore.

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/2d6e5304bca4d045. Report an issue: GitHub.