plandex-ai/plandex · error

no convo message for ID: %s

Error message

no convo message for ID: %s

What it means

Thrown when a description's ConvoMessageId is set but no message with that ID was found among the loaded conversation messages (convoMessagesById lookup returns nil). It means the description points at a message that does not exist in the plan's convo.

Source

Thrown at app/server/types/active_plan_pending_builds.go:49

	}

	convoMessagesById := map[string]*db.ConvoMessage{}
	for _, msg := range convoMessages {
		convoMessagesById[msg.Id] = msg
	}

	activeBuildsByPath := map[string][]*ActiveBuild{}

	for _, desc := range planDescs {
		if (!desc.DidBuild && len(desc.Operations) > 0) || len(desc.BuildPathsInvalidated) > 0 {
			if desc.ConvoMessageId == "" {
				log.Printf("No convo message ID for description: %v\n", desc)
				return nil, fmt.Errorf("no convo message ID for description: %v", desc)
			}

			if convoMessagesById[desc.ConvoMessageId] == nil {
				log.Printf("No convo message for ID: %s\n", desc.ConvoMessageId)
				return nil, fmt.Errorf("no convo message for ID: %s", desc.ConvoMessageId)
			}

			// convoMessage := convoMessagesById[desc.ConvoMessageId]

			// replyParser := NewReplyParser()
			// replyParser.AddChunk(convoMessage.Message, false)
			// parserRes := replyParser.FinishAndRead()

			numAdded := 0
			for _, op := range desc.Operations {

				if desc.DidBuild && !desc.BuildPathsInvalidated[op.Path] {
					continue
				}

				if activeBuildsByPath[op.Path] == nil {
					activeBuildsByPath[op.Path] = []*ActiveBuild{}
				}

View on GitHub (pinned to e2d772072e)

Solutions

  1. Look up the logged ConvoMessageId in the convo_messages table to confirm whether the row was deleted.
  2. Restore the missing convo message or delete the orphaned description record.
  3. If supplying convoMessagesArg, ensure it contains ALL messages for the plan (e.g. the full result of db.GetPlanConvo).
  4. Re-run any migration/backfill that links descriptions to their source messages.

Example fix

// before
builds, err := plan.PendingBuildsByPath(orgId, userId, partialConvo)
// after
fullConvo, err := db.GetPlanConvo(orgId, plan.Id) // pass the complete set
if err != nil { return err }
builds, err := plan.PendingBuildsByPath(orgId, userId, fullConvo)
Defensive patterns

Strategy: validation

Validate before calling

msgIds := map[string]bool{}
for _, m := range convo { msgIds[m.Id] = true }
for _, desc := range descs {
    if desc.ConvoMessageId != "" && !msgIds[desc.ConvoMessageId] {
        return fmt.Errorf("description %s references missing convo message %s", desc.Id, desc.ConvoMessageId)
    }
}

Type guard

func convoMessageExists(id string, byId map[string]*db.ConvoMessage) bool {
    return id != "" && byId[id] != nil
}

Try / catch

builds, err := plan.PendingBuildsByPath(orgId, userId, fullConvo)
if err != nil {
    if strings.Contains(err.Error(), "no convo message for ID") {
        log.Printf("dangling description reference; resync descriptions with convo")
        return resyncDescriptions(plan.Id)
    }
    return err
}

Prevention

When it happens

Trigger: db.GetConvoMessageDescriptions returns a description whose convo_message_id has no matching row in the convo messages table; passing a partial convoMessagesArg that omits the referenced message; messages deleted after descriptions were created.

Common situations: Orphaned description rows after convo message deletion or plan truncation; cross-plan data corruption from a bad migration; caller passing a filtered/partial convo message slice to PendingBuildsByPath.

Related errors


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