I have seen a bug in Sitecore xConnect for a while now, that I have not been able to reproduce enough to be able to make a Sitecore ticket out of it.

Here is the short version: when Sitecore merges two contacts, it merges all the identifiers. But each contact has the same two identifiers, and the merge needs to pick which ones to keep.

Alias (Sitecore.XConnect.Constants.AliasIdentifierSource). This is a random guide that identifies a contact. This is used by EXM a lot because it is the Alias that every contact has. Little known fact: this is added to a contact when it is created, but you can not add it back if you accidentally delete it.

xDB.Tracker This is also a random guide that is only assigned to contacts that were created during a web visit. So if you create a contact with xConnect API, it will not have a xDB.Tracker. Only a web visit will create this identifier. I don't think creating contacts with the API has anything to do with our issue.

When the merge happens, Sitecore has to keep only one of the Alias trackers and one xDB.Tracker, if it exists. The issue we are having is that no xDB.Tracker is being kept on the contact. They are both thrown away, or if one has a tracker and one doesn't, it throws away the good one. Then when the user tries to use the contact for a web visit, we get the error below. xConnect tracker is essentially dead until the user clears their cookies.

4664 16:02:57 ERROR Cannot create tracker.
Exception: Sitecore.Framework.Conditions.PostconditionException
Message: Postcondition 'Contact with identifier '26ef19a4-5f98-1400-0000-05b5b14268ba' must have a tracker identifier.' failed.
Source: Sitecore.Framework.Conditions
   at Sitecore.Framework.Conditions.EnsuresValidator`1.ThrowExceptionCore(String condition, String additionalMessage, ConstraintViolationType type)
   at Sitecore.Framework.Conditions.Throw.ValueShouldNotBeNull[T](ConditionValidator`1 validator, String conditionDescription)
   at Sitecore.Framework.Conditions.ValidatorExtensions.IsNotNull[T](ConditionValidator`1 validator, String conditionDescription)
   at Sitecore.Analytics.XConnect.DataAccess.Pipelines.ConvertFromXConnectContactPipeline.ConvertFromXConnectContactProcessor.Process(ConvertFromXConnectContactPipelineArgs args)
   at (Object , Object )
   at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
   at Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain, Boolean failIfNotExists)
   at Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain)
   at Sitecore.Analytics.XConnect.DataAccess.PipelineBasedModelConverter.<>c__DisplayClass4_0.<ConvertFromXConnectContact>b__0()
   at Sitecore.Analytics.XConnect.Diagnostics.PerformanceCounters.OperationPerformanceMonitorExtensions.Monitor[T](OperationPerformanceMonitorBase monitor, Func`1 operation)
   at Sitecore.Analytics.XConnect.Diagnostics.PerformanceCounters.OperationPerformanceMonitorExtensions.Monitor[T](OperationPerformanceMonitorBase monitor, Func`1 operation)
   at Sitecore.Analytics.Data.ContactRepository.LoadContact(ID contactId)
   at Sitecore.Analytics.Tracking.ContactManager.LoadContact(Guid contactId, Boolean exclusive)
   at Sitecore.Analytics.Pipelines.EnsureSessionContext.LoadContact.Process(InitializeTrackerArgs args)
   at (Object , Object )
   at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
   at Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain, Boolean failIfNotExists)
   at Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain)
   at Sitecore.Analytics.Pipelines.EnsureSessionContext.EnsureSessionContextPipeline.<>c__DisplayClass4_0.<Run>b__0()
   at Sitecore.Analytics.XConnect.Diagnostics.PerformanceCounters.OperationPerformanceMonitorExtensions.<>c__DisplayClass1_0.<Monitor>b__0()
   at Sitecore.Analytics.XConnect.Diagnostics.PerformanceCounters.OperationPerformanceMonitorExtensions.Monitor[T](OperationPerformanceMonitorBase monitor, Func`1 operation)
   at Sitecore.Analytics.DefaultTracker.EnsureSessionContext()
   at Sitecore.Analytics.Pipelines.CreateTracker.GetTracker.Process(CreateTrackerArgs args)
   at (Object , Object )
   at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
   at Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain, Boolean failIfNotExists)
   at Sitecore.Pipelines.DefaultCorePipelineManager.Run(String pipelineName, PipelineArgs args, String pipelineDomain)
   at Sitecore.Analytics.Tracker.Initialize()

This error happens in the convertFromXConnectContact pipeline in the processor

Sitecore.Analytics.XConnect.DataAccess.Pipelines.ConvertFromXConnectContactPipeline.ConvertFromXConnectContactProcessor.

This processor is essentially taking the ID from the analytics cookie, finding the contact in xConnect and getting the tracker identifier to use that in the Sitecore Tracker.

What I am doing is adding a new pipeline, before the  ConvertFromXConnectContactProcessor processor, that checks to see if we have the xDB.Tracker identifier. If we don't, I am adding it. It is 100% a hack. But it saves my users from forever being in a loop of having a broken tracker that they have no idea how to fix.

The Code

The patch config

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
    <sitecore>
        <pipelines>
            <convertFromXConnectContact>
                <processor type="Client.Pipelines.ConvertFromXConnectContactPipeline.LogContactsMissingTracker, Client" patch:before="processor[@type='Sitecore.Analytics.XConnect.DataAccess.Pipelines.ConvertFromXConnectContactPipeline.ConvertFromXConnectContactProcessor, Sitecore.Analytics.XConnect']" />
            </convertFromXConnectContact>
        </pipelines>
    </sitecore>
</configuration>

What this code does is check for the xDB.Tracker before Sitecore looks for it. If it doesn't find it, it calls xConnect and adds it. I find this pipeline executes about once every 4 or 5 days. But when I didn't have this fix, I would get the tracker error about 60 times a day.

If you have a test rig where you can prove out why this error happens, I would love to know so we can get it fixed with Sitecore.