Summary
SpamBayes-dependent ribbon commands are invokable before AppItemEngines.InitAsync() has populated Globals.Engines.InboxEngines. RibbonController.SB returns null during that window, so RibbonViewer.TrainSpam_Click dereferences null and throws a NullReferenceException. The same initialization race affects every ribbon command backed by an engine in InboxEngines (Triage, Project, Context, Actionable).
Environment
- OS/version: Windows 11, Outlook desktop (VSTO add-in host)
- Runtime: .NET Framework 4.8, TaskMaster VSTO add-in
- Command/flags used: Outlook Explorer ribbon, "Train Spam" button clicked immediately after add-in reload
- Data source or fixture: Live Outlook profile;
Globals.AF.Manager.Configuration still resolving
Steps to Reproduce
- Reload the TaskMaster add-in (or restart Outlook) so
AppItemEngines.InitAsync() begins.
- Before
InitAsync() completes, click the "Train Spam" ribbon button in the Explorer ribbon.
- Observe the failure in
RibbonViewer.TrainSpam_Click.
Expected Behavior
Engine-dependent ribbon commands are not invokable until their backing engine in InboxEngines is available. Clicking a not-yet-ready command produces no exception; once InitAsync() completes, the commands become enabled and behave exactly as they do today.
Actual Behavior
RibbonController.SB evaluates Globals?.Engines?.InboxEngines?.TryGetValue("Spam", out var engine) against an empty ConcurrentDictionary and returns null. RibbonViewer.TrainSpam_Click then executes await Controller.SB.TrainAsync(Controller.OlSelection, true), which throws NullReferenceException. Because the handler is async void, the exception surfaces on the message-pump synchronization context rather than at the call site.
Logs / Screenshots
System.NullReferenceException: Object reference not set to an instance of an object.
at TaskMaster.RibbonViewer.TrainSpam_Click(IRibbonControl control)
Relevant source:
TaskMaster/Ribbon/RibbonViewer.cs (TrainSpam_Click, ~line 255-256)
TaskMaster/Ribbon/RibbonController.Intelligence.cs (SB property, ~line 190-202)
TaskMaster/AppGlobals/AppItemEngines.cs (InitAsync, InboxEngines)
Impact / Severity
An unhandled NullReferenceException on a routine ribbon click immediately after add-in reload. The window is short but reliably reachable, and the failure mode is a silent/unhandled async-void throw rather than a recoverable message.
Source
From: docs/features/potential/2026-08-08-ribbon-engine-readiness-guard.md
Summary
SpamBayes-dependent ribbon commands are invokable before
AppItemEngines.InitAsync()has populatedGlobals.Engines.InboxEngines.RibbonController.SBreturnsnullduring that window, soRibbonViewer.TrainSpam_Clickdereferences null and throws aNullReferenceException. The same initialization race affects every ribbon command backed by an engine inInboxEngines(Triage, Project, Context, Actionable).Environment
Globals.AF.Manager.Configurationstill resolvingSteps to Reproduce
AppItemEngines.InitAsync()begins.InitAsync()completes, click the "Train Spam" ribbon button in the Explorer ribbon.RibbonViewer.TrainSpam_Click.Expected Behavior
Engine-dependent ribbon commands are not invokable until their backing engine in
InboxEnginesis available. Clicking a not-yet-ready command produces no exception; onceInitAsync()completes, the commands become enabled and behave exactly as they do today.Actual Behavior
RibbonController.SBevaluatesGlobals?.Engines?.InboxEngines?.TryGetValue("Spam", out var engine)against an emptyConcurrentDictionaryand returnsnull.RibbonViewer.TrainSpam_Clickthen executesawait Controller.SB.TrainAsync(Controller.OlSelection, true), which throwsNullReferenceException. Because the handler isasync void, the exception surfaces on the message-pump synchronization context rather than at the call site.Logs / Screenshots
Relevant source:
TaskMaster/Ribbon/RibbonViewer.cs(TrainSpam_Click, ~line 255-256)TaskMaster/Ribbon/RibbonController.Intelligence.cs(SBproperty, ~line 190-202)TaskMaster/AppGlobals/AppItemEngines.cs(InitAsync,InboxEngines)Impact / Severity
An unhandled
NullReferenceExceptionon a routine ribbon click immediately after add-in reload. The window is short but reliably reachable, and the failure mode is a silent/unhandled async-void throw rather than a recoverable message.Source
From: docs/features/potential/2026-08-08-ribbon-engine-readiness-guard.md