plandex-ai/plandex · error

error reading convo message: %v

Error message

error reading convo message: %v

What it means

GetConvoMessage reads a single message file (<messageId>.json) from the plan's convo directory with os.ReadFile and returns this error wrapping the *PathError on failure. Note there is no IsNotExist special case here (unlike GetPlanConvo), so a missing message — the most common cause — surfaces as this error too.

Source

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

			convo = append(convo, convoMessage)
		}
	}

	sort.Slice(convo, func(i, j int) bool {
		return convo[i].CreatedAt.Before(convo[j].CreatedAt)
	})

	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()

View on GitHub (pinned to e2d772072e)

Solutions

  1. Confirm the messageId (and orgId/planId) is correct and the message still exists: ls <convoDir>/<messageId>.json
  2. If the message was deleted by a plan reset/rollback, treat it as gone and refresh the convo listing
  3. Fix permissions/ownership on the convo directory if the underlying error is permission-denied
  4. Restore the file from backup if it was lost
  5. Check the storage volume for I/O errors if the underlying message indicates a hardware/filesystem issue

Example fix

// caller hardening: treat not-found distinctly
// before
msg, err := db.GetConvoMessage(orgId, planId, msgId)
// after
path := filepath.Join(db.GetPlanConversationDir(orgId, planId), msgId+".json")
if _, err := os.Stat(path); os.IsNotExist(err) {
    return fmt.Errorf("message %s not found", msgId)
}
msg, err := db.GetConvoMessage(orgId, planId, msgId)
Defensive patterns

Strategy: type-guard

Validate before calling

if messageId == "" || !uuid.IsValid(messageId) {
    return fmt.Errorf("invalid message id %q", messageId)
}
path := filepath.Join(getPlanConversationDir(orgId, planId), messageId+".json")
if _, err := os.Stat(path); err != nil {
    if os.IsNotExist(err) {
        return ErrConvoMessageNotFound
    }
}

Type guard

func isConvoMessageNotFound(err error) bool {
    return errors.Is(err, fs.ErrNotExist) || strings.Contains(err.Error(), "no such file")
}

Try / catch

msg, err := db.GetConvoMessage(orgId, planId, messageId)
if err != nil {
    if isConvoMessageNotFound(err) {
        return http.StatusNotFound, fmt.Errorf("message %s not found", messageId)
    }
    return http.StatusInternalServerError, err
}

Prevention

When it happens

Trigger: os.ReadFile fails: the messageId does not exist (client passed a stale/deleted/wrong UUID, or the '.json' suffix handling was bypassed), wrong orgId/planId used to build the path, permission denied, or I/O error on the storage volume.

Common situations: Client or UI holding a reference to a message from before a plan was reset/rolled back; a cross-plan or cross-org message id used by mistake; files lost after an incomplete restore; permissions changed on the data dir.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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