fix: root-fix helper "update available" nag for bundled owners (v1.43 W1)#382
Merged
Conversation
… W1)
The app-embedded Swift helper updates WITH the app, but HelperInstaller
compared its hello version against the standalone .pkg manifest — showing a
perpetual, unclearable "Update available" nag (installing the .pkg can't evict
a bundled owner that owns the socket).
- hello gains an additive `implementation` field ("swift-bundled"/"python-pkg";
absent on older helpers → nil → legacy path). Additive-only wire rule is now
written into Protocol.swift's head comment.
- HelperInstaller gains a `.bundled` state; a pure, testable resolveState()
returns it for a swift-bundled owner REGARDLESS of the manifest. Every
.pkg/older-helper path is preserved exactly.
- Controlled swap: an app-version sentinel `launchctl kickstart -k`s our OWN
LaunchAgent once per app update so the new bundle's helper takes over now —
clearing the nag even for users upgrading from a pre-v1.43 helper whose hello
can't be identified as stale (dual-review P1). DEVID-only.
- Post-swap the UI is reconciled (refreshAfterHelperRespawn + a monotonic-epoch
guard in refresh()) so an in-flight refresh can't leave a stale nag.
- CompanionCLISection renders .bundled as "built-in — updates with the app" and
hides the .pkg-only Check-for-Updates/Uninstall.
Tests: pytest helper (950), swift test HelperSwift (624) + CLIPulseCore default
& -DDEVID_BUILD (0 failures) incl. resolveState regression pins + sentinel
truth table; regression-red proof; app compile matrix (non-DEVID/DEVID macOS,
iOS, MAS archive). Reviewed by codex + agy (2 rounds) + an adversarial workflow
audit; all findings addressed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
JasonYeYuhe
added a commit
that referenced
this pull request
Jul 23, 2026
…#383) Version bump in prep for the v1.43.0 release (W1 nag fix #382 + merged #377/#378/#380/#381). Release-prep only — no DEVID cut, no publish, no ASC. - Apple MARKETING_VERSION 1.42.0 → 1.43.0, CURRENT_PROJECT_VERSION 95 → 96 (all 10 target/config entries). - Android versionName → 1.43.0, versionCode 61 → 62 — solely to satisfy check-versions.sh drift gate; NOT submitting to Play this cycle. Verified: check-versions.sh OK; android ./gradlew testDebugUnitTest (JDK 17) BUILD SUCCESSFUL. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
W1 — structurally fix the helper "update available" nag (v1.43)
Root-fixes the perpetual, unclearable "Update available →1.30.0" nag that DEVID users see in Settings → Managed CLI Helper. Two helpers can own the local UDS socket: the Swift helper embedded in the app bundle (updates with the app) and the standalone
.pkgPython helper.HelperInstaller.refresh()compared whichever owner's hello version against the published.pkgmanifest — so a bundled owner was forever told to "update" a helper it can't update independently (installing the.pkgdoesn't evict a bundled owner that owns the socket).What changed (all additive on the wire)
hellofieldimplementation—"swift-bundled"(Swift helper) /"python-pkg"(Python helper). Older helpers omit it → decoded asnil→ legacy path unchanged. Element-for-element mirror; the additive-only evolution rule is now written intoProtocol.swift's head comment.HelperInstallergains a.bundled(version:)state;refresh()delegates to a pure, testableresolveState(...)that returns.bundledfor a swift-bundled owner regardless of the manifest (and skips the manifest fetch for it). Every.pkg/older-helper path is preserved byte-for-byte.ensureRegistered()is a no-op and the KeepAlive LaunchAgent keeps the old helper process alive until re-login.kickstartBundledHelperIfAppUpdated(...)detects an app-binary change via a persisted app-version sentinel andlaunchctl kickstart -ks our own LaunchAgent once per new app version, so the new bundle's helper takes over immediately. DEVID-only (#if DEVID_BUILD).CompanionCLISectionrenders.bundledas "built-in — updates with the app" and hides the.pkg-only Check for Updates / Uninstall… controls.Why an app-version sentinel and not a helper-version compare (review-driven)
The first cut gated the swap on
implementation == "swift-bundled"+ a helper-version compare. Dual review (codex + agy) independently caught a P1: the existing v1.42 install base runs a bundled helper reporting1.23.0with noimplementationfield, so a field/version-gated swap can't identify it as stale — the nag would persist for exactly the users the fix targets, until re-login. The app-version sentinel sidesteps owner identification entirely: it kickstarts only our own LaunchAgent label (never the separately-labelled.pkghelper) once per new app version, which also covers a helper-binary change that keeps the same version number. This let us drop the app-side version mirror + the 3-way CI pin the first cut added.Testing
pytest helper/— 950 passed (incl.implementation == "python-pkg"hello assertion).swift test— 624 tests (incl."swift-bundled"hello assertion).swift testdefault +-Xswiftc -DDEVID_BUILD— 0 failures. New pure-logic tests forresolveState(bundled-never-nags regression pin,.pkg/older-helper legacy paths preserved, nil/socket/manifest branches) andshouldKickstartHelperForAppUpdate(changed / first-run / same-version / build-bump).test_resolveState_bundledOwnerNeverNagsEvenWhenManifestNewerfails producing exactly the shipped bug (updateAvailable(1.29.0 → 1.30.0)), confirming the test guards it.xcodebuildapp target — non-DEVID macOS, DEVID macOS, iOS, and MAS archive all build (the#if DEVID_BUILDlaunchctlexec never compiles into the sandboxed MAS build).pairedinto the Swift hello). Round 2 caught a P2 post-swap UI race (an in-flightrefresh()capturing the pre-swap helper could leave a stale nag) → fixed with a monotonic-epoch guard inrefresh()(last-started wins, so a slow manifest fetch can't clobber a newer result) +refreshAfterHelperRespawn()that reconciles the UI once the kickstarted helper rebinds; plus P3 comment-accuracy fixes (bundled owners now bypass the.pkgcompare). Final: agy SHIP, codex re-check clean.Known transient
On the first launch after upgrading from a pre-v1.43 helper, there's a ~1–2s window (old helper still owner, before the kickstart's respawn binds) where the section could briefly show the old nag before settling to
.bundled; it self-heals via the existing re-probe hook.🤖 Generated with Claude Code