fix(desktop): keep first launch quiet until the user is onboarded
From the biweekly fresh-Mac onboarding walkthrough (https://app.superset.sh/page/onboarding-walkthrough-on-a-fresh-mac-udhhym): a filmed production first launch of desktop v2.0.2 on a clean macOS 26 VM.
Findings and root causes
1. Two permission prompts at launch, before any UI.
- Local Network: we ask for it on purpose.
requestLocalNetworkAccess()(apps/desktop/src/main/lib/local-network-permission.ts) sends one mDNS packet to224.0.0.251:5353so macOS shows the prompt. It ran at boot inapps/desktop/src/main/index.ts:515(on main). Nothing else in desktop or host-service touches the LAN at boot: no other dgram, Bonjour, or multicast code, and listening sockets do not trigger the prompt. The probe came from #737 so that LAN dev servers work before a terminal hits them. It is not needed before the user can open a terminal. - Notifications:
registerWithMacOSNotificationCenter()(index.ts:513) shows a silent notification so the app gets listed in System Settings > Notifications. #1492 added it because on macOS 26 notifications were never delivered without it. That is what causes the prompt. - The same block also ran
requestAppleEventsAccess()(index.ts:514, #1567). It has the same no-context problem, so it moves with the other two.
2. Writes to other tools' config dirs before sign-in. setupAgentIntegrations() ran at boot (index.ts:648) with no auth check. It writes hooks and settings for every supported agent. A probe against an empty $HOME reproduces the list from the walkthrough: .claude .codex .config/{amp,devin,muse} .cursor .factory .gemini .grok .kimi-code .mastracode .omp .pi .vibe. Managed skills, run from the same call, write ~/.agents/skills (managed-skills.ts:401). syncInstalledPluginMcpServers() (index.ts:656) ran next to it. After sign-in, <OrganizationPluginSync /> (_authenticated/layout.tsx:350) also ran plugins sync during onboarding, because onboarding renders inside the authenticated layout.
3. DMG volume "Superset 2.0.2-arm64". electron-builder.ts set no dmg.title. dmg-builder's default is ${productFilename} ${shortVersion}${arch}.
What changed
- New
main/lib/post-onboarding-setup: it holds a list of boot steps and runs them once per process, but only after the renderer asks for them. If one step fails, it logs the error and runs the rest. The renderer asks through a new electronTrpc mutationpostOnboardingSetup.run.<PostOnboardingSetup />sends it whensession.user.onboardedAtis set. - These steps move from boot into that list: Notification Center registration, Apple Events probe, Local Network probe,
setupAgentIntegrations(with the disabled-agent/skill mirror writes), and plugin MCP sync.setAgentSetupTemplatesDir, the CLI shims (~/.superset/bin), andfinishPendingOrganizationForgetstill run at boot. <OrganizationPluginSync />renders only for onboarded users.dmg.titleis${displayName} ${version}, so the volume is "Paradev 2.0.2".displayNamecomes fromDESKTOP_DISPLAY_NAME, which is set on the Build Electron app step. Canary gets "Paradev Canary ${version}".productName,appId, bundle name,artifactName, update feed, and protocol scheme do not change. The arch suffix is no longer in the volume name. It is still in the DMG file name.
Existing users: nothing is removed. Provisioning stays declarative. The desired set and the reaping do not change, only when they run. An onboarded user reaches the gate a moment after launch, so hooks, skills, and MCP entries refresh as before, just a little later. Files from earlier runs stay in place in the meantime. If the session cannot be confirmed (offline screen), the refresh waits until the next launch that reaches the dashboard. A new user is provisioned when onboarding finishes, before they can open a terminal. The prompts also appear at that point, right as the dashboard opens.
Not changed: provision only installed agents
I did not skip agents that are not installed. Claude, Gemini and others take hooks only from their global config, and nothing re-provisions while the app runs. If we skipped ~/.claude because it is absent, a new user who installs Claude Code after Paradev (a common first-day order) would get no agent status or notifications until the next app restart. Doing this safely needs a re-provision trigger, for example when a terminal starts an agent or when an agent's config dir appears. That is a separate change.
Verification
bun test src/main/lib/post-onboarding-setup: 4 pass. Before keeping the tests, I broke the run-once/requested guard and 3 of 4 failed.apps/desktopbun run typecheck: exit 0, no errors.apps/desktopbun test: 9024 pass, 36 fail. All 36 are real-git and login-shell suites (getStatus, getBranches, createWorktree, shell env, and others; e.g.git update-ref refs/remotes/origin/mainfails). They fail in this sandboxed worktree, and none of them touch files in this diff.bun run lint:fix: clean. No new user-facing strings, so nocheck:i18nchange.- Probe:
setupAgentIntegrations()against an empty temp$HOMEcreates the dirs listed above (confirms the root cause for item 2). - Builder config: loading
electron-builder.ts/.canary.tswithDESKTOP_DISPLAY_NAME=Paradevgivesdmg.titleParadev ${version}/Paradev Canary ${version}. dmg-builder 26.8.1computeVolumeNamereplaces${version}. - Not verified: the macOS permission prompts and the mounted DMG volume name. Both need a signed production build on a clean Mac. I did not run the dev desktop app over CDP for this change. The next fresh-Mac walkthrough should confirm: no prompts and no new dotfiles before sign-in and onboarding, the prompts and hooks appear once onboarding finishes, and the volume reads "Paradev 2.0.2".
Summary by cubic
Defers macOS permission prompts and writes to other tools' config directories until the user finishes onboarding, so a first launch on a fresh Mac no longer shows two permission prompts or creates dotfiles before sign-in.
- New
postOnboardingSetupruns deferred boot steps once per process, triggered by the renderer via a tRPC mutation whensession.user.onboardedAtis set. - Deferred steps: Notification Center registration, Apple Events and Local Network probes, agent hook/skill provisioning, and plugin MCP sync. CLI shims, the templates dir, and pending organization cleanup still run at boot.
- Organization plugin sync now only renders for onboarded users.
- The DMG volume name now uses the display name and version (e.g. "Paradev 2.0.2") instead of the product name with an arch suffix.
Side effects: existing users get the same provisioning, just a moment after launch. If the session can't be confirmed (offline), the refresh waits until a launch that reaches the dashboard. A new user is provisioned when onboarding finishes, before they can open a terminal.
Summary by CodeRabbit
- New Features
- Post-onboarding setup now runs after onboarding is complete, including agent integrations, plugin services, and permission requests. If one setup task fails, later tasks can still proceed.
- macOS installation disk images now display the app’s name and version in the mounted volume title.