Skip to content

feat(updates): silence update mechanics — version/restart churn → logs, not the user#783

Open
JKHeadley wants to merge 1 commit into
mainfrom
echo/quiet-update-mechanics
Open

feat(updates): silence update mechanics — version/restart churn → logs, not the user#783
JKHeadley wants to merge 1 commit into
mainfrom
echo/quiet-update-mechanics

Conversation

@JKHeadley

@JKHeadley JKHeadley commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Problem

The Agent Updates topic was flooding with update mechanics the user can't use — raw version numbers and restart plumbing. Real messages from a live Updates topic:

  • Just updated to v1.3.181. Restarting to pick up the changes.
  • Update to v1.3.217 was applied but I'm still running v1.3.218. The next restart should pick it up.
  • Update v1.3.215 queued — rolling into the pending restart at 02:21…

mature-update-announcements (#698) made the feature-announcement path silent-by-default. It never touched the mechanics path — these aren't announcements, they're hardcoded restart/version status. So the noise remained. (User feedback, 2026-06-04: "notifications that reference features the user has no clue about.")

What this does

A single pure policy (src/core/updateNotifyPolicy.ts) classifies every update notification into mechanics | interruption | actionable | failure-escalated. AutoUpdater.notify() and the restart-handshake emit in server.ts gate on it at their single notify funnel. Default kind is mechanics (silent) so any future un-audited callsite stays quiet rather than spamming.

The user now hears about an update ONLY when:

  1. A genuinely new capability ships (the maturity layer — unchanged).
  2. A restart is actually interrupting their active work right now — a plain, version-free "back in a few seconds", never "v1.3.X".
  3. An update is genuinely stuck after retries.

Everything else — version churn, restart-batch coordination, self-healing version skew, transient apply failures that retry — goes to the logs only. All restart/interruption copy was rewritten version-free.

Opt into a single quiet "just refreshed in the background" heartbeat with updates.backgroundRefreshHeartbeat: true (default false = full silence); scoped so it can never re-introduce version churn.

Callsite map

Source Kind Reaches user?
version-skew nudge / transient apply failure / cascade-batch mechanics no (silent)
handshake mismatch (non-escalated) — "vX applied but still running vY" mechanics no (silent)
restart narration / max-deferral / deferral threshold warnings interruption yes (version-free)
manual-update-available (auto-apply off) actionable yes (version-free)
handshake mismatch (escalated) — restart won't take failure-escalated yes (version-free)

Tests

  • New: update-notify-policy (both sides of every branch) + update-notify-routing (funnel wiring integrity: mechanics never calls sendToTopic, the three reaching kinds do, option-B gating). 16/16.
  • New: PostUpdateMigrator-quietUpdateMechanics (migration parity). 5/5.
  • Updated to the new contract: notification-spam-prevention, auto-updater-failures, graceful-updates-phase2, update-notification-topic-lock (mechanics silent; interruption/actionable assert no \d+\.\d+\.\d+ leaks).
  • Regression-safe: AutoUpdater, AutoUpdater-cascade-dampener, restart-window, e2e self-heal-cascade-and-drift, integration updates-status-restart-immediately-route, stall-recovery-e2e. tsc build clean.

Migration parity & awareness

  • Behavior ships in code (no config migration needed — absence of the flag = full silence).
  • Agent awareness: "Quiet update mechanics" block added to the CLAUDE.md template + backfilled to existing agents via PostUpdateMigrator under its own content-sniff guard.

Process

Tier 1 via /instar-dev (ELI16 + side-effects review + release fragment). The gate flagged Tier 1 as below the risk floor (PostUpdateMigrator touch + new config key) — recorded, not blocked; both are the lowest-risk instances (idempotent append-only awareness block; optional default-false flag). User pre-approved the approach (option A — full silence).

Spec: docs/specs/quiet-update-mechanics.md

🤖 Generated with Claude Code

ELI16 — version/restart churn goes to the logs, not to you

Your Updates channel was filling with messages you can't use: "Just updated to v1.3.217. Restarting…", "still running v1.3.218, the next restart should pick it up", "rolling into the pending restart at 02:42" — meaningless version churn. This change sorts every update message into four buckets before it can reach you: mechanics (version/restart plumbing → logs only), interruption (a restart is about to hit your active work → a plain "back in a few seconds", no version numbers), actionable (auto-updates are off, so you'd have to say "update"), and stuck (an update genuinely failed after retries). Only the last three reach you. The default bucket is silent-mechanics, so any future message a developer forgets to classify stays quiet instead of accidentally spamming — forgetting fails toward silence, not noise. An opt-in flag exists for people who'd rather get one quiet "just refreshed in the background" note than total silence, and that flag can only ever surface that single message — it can't reopen the flood. Nothing important is lost: real interruptions and truly-stuck updates still reach you, just without the jargon.

…s, not the user

The Agent Updates topic was flooding with update *mechanics* the user can't
use — raw version numbers and restart plumbing ("Just updated to v1.3.217.
Restarting…", "vX applied but I'm still running vY", cascade-batch "rolling into
the pending restart at HH:MM"). mature-update-announcements (#698) silenced the
*feature-announcement* path; this silences the *mechanics* path.

New pure module src/core/updateNotifyPolicy.ts classifies every update
notification into mechanics | interruption | actionable | failure-escalated;
AutoUpdater.notify() and the restart-handshake emit in server.ts gate on it at
their single notify funnel. Default kind is `mechanics` (silent) so any future
un-audited callsite stays quiet instead of spamming. The user now hears about an
update ONLY for: a new capability (the maturity layer), a restart actually
interrupting their active work right now (plain, version-free "back in a few
seconds"), or a genuinely stuck update. All restart/interruption copy rewritten
version-free.

Opt into a single quiet "just refreshed in the background" heartbeat with
`updates.backgroundRefreshHeartbeat: true` (default false = full silence); it can
never re-introduce version churn.

Tests: update-notify-policy (both sides of every branch) + update-notify-routing
(funnel wiring integrity). Updated notification-spam-prevention,
auto-updater-failures, graceful-updates-phase2, update-notification-topic-lock to
the new contract (mechanics silent; interruption/actionable version-free).
Agent awareness via CLAUDE.md template + PostUpdateMigrator (own content-sniff
guard). Spec + ELI16 + side-effects review + release fragment included.

Spec: docs/specs/quiet-update-mechanics.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
instar Ready Ready Preview, Comment Jun 5, 2026 3:13am

Request Review

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant