NEXO 7.11.0 — Doc-only releases stop forcing MCP restarts

Published 2026-04-27. Minor release. Replaces a version-string-only restart gate with a content-aware one.

Why

Before 7.11.0 every nexo update that bumped version.json wrote mcp-restart-required.json, and every connected MCP client — Claude Code, Codex, Claude Desktop — had to drop its session and reconnect. That cost was unconditional, even when the release changed nothing the running server actually executes. v7.10.1 was a README-only patch and still triggered a forced restart for everyone. So did v7.10.0 in the gh-pages content patch that followed it. Operators were paying a session-restart tax for byte-identical Python.

What changed

The runtime now computes a SHA-256 digest over every .py file under src/ that the live MCP server can import — excluding subprocess-only subtrees (scripts/, tests/, migrations/, crons/) plus the obvious noise (__pycache__/, node_modules/, .git/). Anything outside that set — markdown, JSON, templates, presets, CHANGELOG.md, marketing files, gh-pages assets — never shifts the digest.

nexo update captures the pre-update digest, applies the new code, recomputes the digest, and only writes the restart marker when the digest actually changed (or the release explicitly opted in via "force_restart": true in version.json). Doc-only releases now end with the line MCP source unchanged (no .py byte changed) — no restart needed. instead of forcing every operator to reconnect.

The running server caches its own fingerprint at startup (prime_process_fingerprint() next to the existing prime_process_version()), so the in-process check is content-aware too: resolve_restart_required() compares installed_fingerprint against process_fingerprint and only falls back to the legacy version-string check when the fingerprint cannot be computed.

Conservative fallback

Learning #186 says: never silently leave a process running stale code after nexo update. The new gate honors it. If compute_mcp_runtime_fingerprint returns the empty string on either side — missing source tree, unreadable file, fresh install — the gate writes the marker as it always did. The new logic is allowed to skip a restart that wasn't going to change behavior; it never misses one that would.

Escape hatch

For releases that change behavior in ways the fingerprint can't see — e.g. the wire format of a config file the server reads via json.load rather than import — the releaser can set "force_restart": true in version.json for that specific release. The marker is then written even when the fingerprint matches. Reason becomes brain_update_force and shows up in nexo mcp-status.

Behavior matrix

Inspection

nexo mcp-status --json now exposes installed_fingerprint, process_fingerprint, fingerprint_match. The reason field on a forced restart is one of marker_required, marker_corrupt, fingerprint_mismatch, or the legacy version_mismatch fallback. Marker schema bumped to v2 with optional from_fingerprint / to_fingerprint; the v1 marker reader stays backwards-compatible.

Tests

14 new tests in tests/test_runtime_fingerprint.py covering determinism, doc-only no-shift, every excluded subtree, missing-dir empty-string, fingerprint-match no-restart, fingerprint-mismatch restart, fallback to version mismatch, marker-always-wins, and the force_restart opt-in. 4 new tests in tests/test_packaged_update_runtime.py covering the npm-install path.

Full changelog entry → · docs/runtime-fingerprint.md