vxcontrol/pentagi · error

failed to get termlog: %w

Error message

failed to get termlog: %w

What it means

GetMsg fetches a single terminal log row by message ID via db.GetTermLog (no flow scoping in the query itself). Any failure — most often sql.ErrNoRows for a deleted/never-existing message ID — is wrapped with this message. Transient DB errors are wrapped identically.

Source

Thrown at backend/pkg/controller/termlog.go:87

		Text:        database.SanitizeUTF8(msg),
		ContainerID: containerID,
		FlowID:      tlw.flowID,
		TaskID:      database.Int64ToNullInt64(taskID),
		SubtaskID:   database.Int64ToNullInt64(subtaskID),
	})
	if err != nil {
		return 0, fmt.Errorf("failed to create termlog: %w", err)
	}

	tlw.pub.TerminalLogAdded(ctx, termLog)

	return termLog.ID, nil
}

func (tlw *flowTermLogWorker) GetMsg(ctx context.Context, msgID int64) (database.Termlog, error) {
	msg, err := tlw.db.GetTermLog(ctx, msgID)
	if err != nil {
		return database.Termlog{}, fmt.Errorf("failed to get termlog: %w", err)
	}

	return msg, nil
}

func (tlw *flowTermLogWorker) GetContainers(ctx context.Context) ([]database.Container, error) {
	containers, err := tlw.db.GetFlowContainers(ctx, tlw.flowID)
	if err != nil {
		return nil, fmt.Errorf("failed to get containers: %w", err)
	}

	return containers, nil
}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Check errors.Is(err, sql.ErrNoRows) and map to 404
  2. Confirm the message row still exists (retention may have purged it)
  3. Use the correct flow's FlowTermLogWorker for authorization before fetching
  4. Validate msgID > 0 and comes from a trusted source
  5. Retry only transient connection errors

Example fix

// before
msg, err := worker.GetMsg(ctx, msgID)
if err != nil { return err } // 500 for missing rows
// after
msg, err := worker.GetMsg(ctx, msgID)
if errors.Is(err, sql.ErrNoRows) { return nil, ErrTermlogNotFound }
if err != nil { return err }
Defensive patterns

Strategy: type-guard

Validate before calling

func validMsgID(id int64) bool { return id > 0 }

Type guard

func termlogNotFound(err error) bool { return errors.Is(err, sql.ErrNoRows) }

Try / catch

msg, err := w.GetMsg(ctx, msgID)
if termlogNotFound(err) {
    return nil, httpErr(http.StatusNotFound, "termlog %d not found", msgID)
}
if err != nil {
    return nil, fmt.Errorf("get termlog: %w", err)
}

Prevention

When it happens

Trigger: Requesting a msgID that was deleted by retention/cleanup, an ID from another flow via a mismatched worker handle, or during a DB outage.

Common situations: Frontend subscription replays referencing pruned rows; clients caching message IDs across environment resets; pagination cursors pointing past deleted data.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/cd461655eb773bbf. Report an issue: GitHub.