Skip to content

CLI talks to out-of-date daemon after upgrade (no version check) #142

@obj-p

Description

@obj-p

Problem

The daemon persists across CLI invocations (ADB-style, per AGENTS.md). When the user upgrades previewsmcp (via Homebrew, `brew upgrade` / reinstall; or via `swift build` / source), the new CLI binary starts talking to the old daemon that's still running. The daemon was booted from the previous binary version and stays alive until manually killed, rebooted, or explicitly told to exit.

Symptoms:

  • New CLI features that depend on new daemon behaviors silently misbehave.
  • Internal protocol tweaks (e.g., the v0.12.0 heartbeat channel and stall detection) can mismatch between versions; the CLI's stall timer may trip on an old daemon that doesn't emit heartbeats.
  • Error messages shaped for the new daemon don't match old-daemon responses.

Reproducer

# Install old version
brew install obj-p/tap/previewsmcp  # say, v0.11.3

# Start daemon (any CLI command does this transparently)
previewsmcp status

# Upgrade
brew upgrade obj-p/tap/previewsmcp  # to v0.12.0

# CLI is new, daemon is still old:
previewsmcp --version   # shows 0.12.0
ps aux | grep "previewsmcp serve --daemon"  # shows process started by 0.11.3 binary

# User commands now go to the stale daemon:
previewsmcp logs        # new subcommand — works, since it's CLI-only
previewsmcp run ...     # goes to old daemon — may miss new features or trip stall timer

Options, ranked

1. Version handshake in the daemon's MCP initialize response (recommended)

Daemon embeds its binary version in its MCP `serverInfo.version` (already the case via PreviewsMCPCommand.version). On connect, the CLI compares server.serverInfo.version against its own compile-time version. On mismatch:

  • CLI logs to stderr: "upgraded: restarting daemon (was X, now Y)"
  • CLI calls `kill-daemon`, polls for shutdown
  • CLI spawns a fresh daemon with the new binary
  • CLI reconnects

This covers every install path (Homebrew, SPM, source build, IDE).

2. Homebrew post-install hook

Formula runs `previewsmcp kill-daemon` on upgrade. Clean for Homebrew users, but doesn't help anyone else. Useful as a secondary defense even with (1) in place — short-circuits the version check since the daemon is already dead before the new CLI connects.

3. Startup banner / warning only

On version mismatch, print a warning and continue. Low-effort, but requires users to act — most won't.

Why this matters more now

v0.12.0 introduces client-server protocol coupling that v0.11.3 didn't have:

  • Daemon emits 2s heartbeat `LogMessageNotification` (new)
  • CLI's `DaemonClient.withDaemonClient` has a 30s stall timer that expects those heartbeats
  • New CLI talking to old daemon: 30s after connect, stall timer trips and disconnects

So v0.12.0 users who auto-upgrade over a running v0.11.x daemon will hit stall-disconnect errors on every command until they manually `kill-daemon`. That's a regression users will hit by simply doing `brew upgrade`.

Suggested scope for the fix

First PR: (1) alone. Handles every install path and fixes the v0.11→v0.12 upgrade specifically.

Follow-up: (2) as defense-in-depth, once the formula is touched for other reasons.

🤖 Filed from a Claude Code session after noticing the symptom on upgrade.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions