fix: the disclosure card claimed telemetry was on while local-only forced it off - #419
Merged
Merged
Conversation
…rced it off
`privacy.localOnlyMode` overrides the anonymous-telemetry switch inside
`UserDefaultsAnonymousTelemetryStore.isEnabled`. That is deliberate: the master
switch promises to skip all cross-app data sources, and making someone find a
second switch to stop a second kind of network call would make the first switch
a lie.
The consequence is that the switch's own value is not what the app does, and
every surface that renders it has to say so. Settings › Privacy does: it
disables the control and reads "Off — local-only mode covers this too."
The first-launch disclosure card did not. To a local-only user it stated, in
the present tense, that CLI Pulse "reports two things: that it was installed,
and whether it ever found a CLI to track" — above a switch showing ON that sent
nothing. Two surfaces in the same app disagreed about whether data was leaving
the machine, and the one that was wrong was the one making the disclosure. That
card is the entire justification for the switch defaulting ON, so it is the
worst place in the app to be inaccurate about what is collected.
The card now takes the same treatment as Settings: the explanation says nothing
is being sent and describes the collection conditionally, and the toggle is
disabled with the same wording.
WHY A GUARD SCRIPT AND NOT A TEST
Both surfaces are SwiftUI views in the app target, which has no test bundle —
the same blind spot that let the activation-latch defect ship. So this adds
two complementary checks:
* AnonymousTelemetryDisclosureGateTests.test_aShutGateAlwaysHasAUserVisibleReason
pins that the flag is CORRECT: across all four combinations of
localOnlyMode x the user's switch, the advertised state must equal the real
gate, and a gate shut for a reason the switch does not show must set
telemetrySuppressedByLocalOnly. It asserts the invariant rather than any
wording, so a future third surface is covered by the same rule.
* scripts/check_telemetry_switch_discloses_suppression.sh pins that every
surface USES it: any file binding the switch two-way must reference
telemetrySuppressedByLocalOnly. Verified in both directions — it passes on
this branch and fails on the v1.45 card. It also fails if it ever matches
fewer than two surfaces, so a rename cannot quietly make it vacuous.
Wired into the existing "Helper guards" CI job.
swift test: passes. xcodebuild Release: BUILD SUCCEEDED.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
JasonYeYuhe
force-pushed
the
fix-disclosure-card-localonly
branch
from
August 7, 2026 10:25
d283d15 to
b7cd6e1
Compare
JasonYeYuhe
added a commit
that referenced
this pull request
Aug 7, 2026
Two fixes to the v1.45 anonymous-install telemetry, both merged already: #418 activation was never sent on the launch that shows the disclosure card — the coordinator's latch recorded having TRIED rather than having SUCCEEDED, so the "Got it" tap hit a stale guard. It hit upgrading users hardest, which is the cohort dominating v1.45's first days, and biased the funnel pessimistically. #419 the disclosure card told local-only users, in the present tense, that statistics were being sent. They were not — localOnlyMode already forces telemetry off inside the store. Settings said so; the card did not. DEVID + Homebrew only. No App Store submission is prepared here, for two reasons: iOS 1.45.0 is still WAITING_FOR_REVIEW and a second version cannot sit in review beside it, and release-surface changes are the owner's call (feedback_appstore_update). macOS 1.45.0 is already READY_FOR_SALE, so MAS users keep the v1.45 behaviour until a separate submission is made. Android versionName/versionCode bumped in step with Apple to satisfy the v1.21 G6 drift gate; no Android release is implied. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
JasonYeYuhe
added a commit
that referenced
this pull request
Aug 7, 2026
Ships the helper-agent self-heal (#425), which missed the 1.46.0 cut by three commits. That is the most user-visible fix of the day and 1.46.0 does not have it: HelperAgentHealth.swift is on main but absent from the published 1.46.0 DMG. WHY IT MATTERS MORE THAN THE TELEMETRY FIXES IT TRAVELS WITH `yyh.CLI-Pulse.helper.agent` can be registered and still have never executed. Measured on a real machine: 22,138 consecutive failed spawns over ~11 days, while every signal the app used said healthy — SMAppService `.status` returned `.enabled`, `register()` returned without throwing, and `launchctl kickstart` exited 0 on a job that failed to exec milliseconds later. A dead helper means no provider data, which means CLI Pulse shows nothing at all. That is the first-value failure the v1.45 telemetry was built to measure, happening silently, to users who then have no reason to come back. ALSO IN THIS RELEASE (both already shipped in 1.46.0, restated for users who skip it): the disclosure card no longer claims statistics are being sent while local-only mode suppresses them (#419), and activation is no longer lost on the launch that shows the disclosure card (#418). DEVID + Homebrew only. iOS 1.45.0 is still WAITING_FOR_REVIEW and a second version cannot sit in review beside it; App Store submission stays the owner's call. swift test: 2678 passed, 0 failures, 4 skipped — including 27 helper-agent tests. Version drift gate passes (Apple 1.47.0 == Android 1.47.0, code 66). Co-authored-by: Claude Opus 5 <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.
Second of the findings from the v1.45 telemetry audit (first is #418, independent — different files, either can merge first).
The problem
privacy.localOnlyModeoverrides the telemetry switch insideUserDefaultsAnonymousTelemetryStore.isEnabled. Deliberate: the master switch promises to skip all cross-app data sources, and a second switch would make the first one a lie.So the switch's value is not what the app does. Settings › Privacy handles that — disables the control, says "Off — local-only mode covers this too."
The first-launch disclosure card did not. To a local-only user it stated in the present tense that CLI Pulse "reports two things: that it was installed, and whether it ever found a CLI to track" — above a switch reading ON that sent nothing.
Two surfaces in the same app disagreed about whether data was leaving the machine, and the wrong one was the one making the disclosure. That card is the entire justification for the switch defaulting ON, which makes it the worst place in the app to be inaccurate about collection.
The fix
Same treatment as Settings: the explanation describes collection conditionally and leads with "Local-only mode is on, so CLI Pulse is sending nothing at all"; the toggle is disabled with the same wording.
Why a guard script and not just a test
Both surfaces are SwiftUI views in the app target, which has no test bundle — the same blind spot that let the activation-latch defect in #418 ship. Two complementary checks:
test_aShutGateAlwaysHasAUserVisibleReasonlocalOnlyMode× user switch, advertised state must equal the real gate, and a gate shut for an unshown reason must settelemetrySuppressedByLocalOnlyscripts/check_telemetry_switch_discloses_suppression.shThe test asserts the invariant rather than any wording, so a future third surface is covered by the same rule. The script was verified in both directions: passes here, fails on the v1.45 card. It also fails if it ever matches fewer than two surfaces, so a rename can't quietly make it vacuous — this repo has had a guard that was wired into nothing and sat idle.
Wired into the existing "Helper guards" CI job.
Verification
swift test—AnonymousTelemetryDisclosureGateTests+PrivacySettingsTestspass, 13 tests.xcodebuild -scheme "CLI Pulse Bar" -configuration Release— BUILD SUCCEEDED.Note on wording
The v1.45 telemetry UI strings are English literals not present in any
.lprojcatalog, so the new string follows the same pattern and falls back like its neighbours. Localizing the telemetry UI as a whole is a separate decision, not smuggled in here.🤖 Generated with Claude Code