If you’re building a macOS menu bar app with rumps or raw pyobjc, you’ll hit this within the first hour.

The pattern: you want to hide the Dock icon (it’s a menu bar app, no Dock presence needed). The standard call is:

from AppKit import NSApplication
app = NSApplication.sharedApplication()
app.setActivationPolicy_(1)  # 1 = accessory (no Dock)

Run it at import time. Get AttributeError: 'NoneType' object has no attribute 'setActivationPolicy_'.

The Real Sequence

NSApplication.sharedApplication() returns the singleton NSApplication instance. The catch: that instance doesn’t exist until the run loop starts. Calling sharedApplication() before rumps.App.run() or any other event-loop entry returns None.

This isn’t documented loudly anywhere. The pyobjc bridge will happily return None rather than raise, so your code blows up on the next attribute access with a confusing message.

The Fix

Defer to a timer that fires after the run loop is alive:

import rumps
from AppKit import NSApplication

class MenuBarApp(rumps.App):
    def __init__(self):
        super().__init__("My App", icon="icon.png")

def hide_dock_icon(_):
    NSApplication.sharedApplication().setActivationPolicy_(1)

app = MenuBarApp()
rumps.Timer(hide_dock_icon, 0.1).start()
app.run()

The Timer fires 0.1s after the run loop starts. By then, sharedApplication() returns the real instance and the call succeeds.

You can also use rumps.timer decorators or schedule via NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_ if you want to feel like a real Cocoa programmer. Same effect.

The Generalization

Any pyobjc API that depends on the AppKit run loop will silently return None if you call it too early. The fix is always the same: defer the call until the loop is running. Module-level code runs before the loop. Methods on your App subclass that fire after app.run() run after the loop. Anything in between, schedule with a timer.

Niche, but if you’re building a status bar utility, this exact bug is in your future.