Skip to content

DevFlow agent can remain unreachable on macOS AppKit after reporting startup #441

Description

@davidortinau

Summary

Microsoft.Maui.DevFlow.Agent can remain unreachable in a Microsoft.Maui.Platforms.MacOS AppKit head when startup work blocks the main thread after DidFinishLaunching.

The registration path queues DevFlowAgentService.Start(...) through the MAUI dispatcher, then prints that the agent started before the queued delegate binds the HTTP listener. If the AppKit main thread does not return to the run loop promptly, the broker has no agent and the configured port does not listen even though startup appears successful.

Environment

  • Microsoft.Maui.DevFlow.Agent: 0.25.0-dev
  • Package repository commit: 386a02dab30129d034473be2aa7f43ba234e02f3
  • DevFlow CLI: 0.1.0-preview.12.26368.2+229f764fd688754497fe5822213e7b13b4e9caa3
  • TFM: net11.0-macos
  • SDK: 11.0.100-preview.7.26381.103
  • macOS: 26.6, Apple Silicon
  • Platform package: Microsoft.Maui.Platforms.MacOS

Reproduction

  1. Create a macOS AppKit MAUI head using Microsoft.Maui.Platforms.MacOS.
  2. Register DevFlow in Debug:
builder.AddMauiDevFlowAgent(options => options.Port = 9225);
builder.AddMauiBlazorDevFlowTools();
  1. Start the DevFlow broker.
  2. Launch the app with startup work that can hold the AppKit main thread after DidFinishLaunching, such as SecureStorage/Keychain access or local database initialization.
  3. Run:
maui devflow diagnose
maui devflow -ap 9225 agent status
lsof -nP -iTCP:9225 -sTCP:LISTEN

Actual behavior

  • The app process remains alive.
  • The broker reports agent_count=0.
  • Port 9225 is closed.
  • agent status returns Cannot connect to agent at localhost:9225.
  • The registration implementation queues service.Start(app, app.Dispatcher) through app.Dispatcher.Dispatch(...) and prints Agent started on port N immediately after queuing, not after the HTTP listener binds.

This makes the debugging agent unavailable precisely while the UI thread is blocked.

Expected behavior

  • The agent HTTP listener starts before later AppKit startup work can block the run loop.
  • The success log appears only after the listener is bound.
  • Broker registration and direct agent status become available reliably.

Verified workaround

A Debug-only bridge resolves the already-registered DevFlowAgentService after base.DidFinishLaunching(...) returns and starts it synchronously on the AppKit main thread:

public override void DidFinishLaunching(NSNotification notification)
{
    base.DidFinishLaunching(notification);

#if DEBUG
    var service = Services?.GetService<DevFlowAgentService>();
    var app = Application.Current;
    var dispatcher = app?.Dispatcher ?? Dispatcher.GetForCurrentThread();

    if (service is { IsRunning: false } && app is not null)
    {
        service.Start(app, dispatcher);
    }
#endif
}

The existing broker registration is reused; no second service or broker registration is created. Start is idempotent when the upstream queued delegate later runs.

With this workaround verified in a real app:

  • Broker reports one agent.
  • Port 9225 listens.
  • agent status identifies the correct project, net11.0-macos, and macOS.
  • Native UI, screenshots, logs, and Blazor WebView CDP commands work.

Suggested fix

For the macOS AppKit registration path, start the already-created service synchronously when the lifecycle callback is already on the AppKit main thread, or otherwise await/confirm listener startup before logging success. StartServerOnly could remain a fallback when Application.Current is unavailable.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions