Skip to content

Restart the daemon after a cask upgrade, and bump to 0.1.4 - #10

Merged
TerrifiedBug merged 2 commits into
mainfrom
fix/cask-restarts-daemon
Aug 19, 2026
Merged

Restart the daemon after a cask upgrade, and bump to 0.1.4#10
TerrifiedBug merged 2 commits into
mainfrom
fix/cask-restarts-daemon

Conversation

@TerrifiedBug

Copy link
Copy Markdown
Owner

Closes #6, then bumps the version literal so v0.1.4 can be tagged.

Ordering matters here: the cask is regenerated by the release workflow on every tag, and Homebrew runs the new cask's postflight during an upgrade. So merging this before tagging means the 0.1.3 → 0.1.4 upgrade itself restarts the daemon — not the one after it. Without it, anyone upgrading to 0.1.4 keeps running 0.1.3 in memory and would conclude the new transcript-ready banner from #9 simply does not work.

The fix

postflight do
  system_command "/bin/launchctl",
                 args:         ["kickstart", "-k", "gui/#{Process.uid}/com.terrifiedbug.yap"],
                 must_succeed: false
end

Not an uninstall stanza, for the reasons already recorded in the cask heredoc: Homebrew runs those on upgrade too, where launchctl would delete the LaunchAgent plist (switching launch-at-login off on every upgrade) and quit would be followed by open -b, starting the daemon outside launchd where neither yap stop nor the login item can reach it. kickstart -k replaces the job launchd already owns.

Measured, not assumed

The open question in the issue was whether restarting could interrupt a dictation press or an in-progress meeting recording destructively. A throwaway LaunchAgent mimicking yap's signal posture settled it:

Question Measured
Signal kickstart -k delivers SIGTERM, not SIGKILL — the handler ran
After a caught SIGTERM and a clean exit 0 launchd started the job again ~5 s later
kickstart against a label with no job exit 113, Could not find service

yap already installs DispatchSource handlers for SIGINT/SIGTERM that route to NSApp.terminate(nil), so SIGTERM runs applicationWillTerminate: a live session is stopped cleanly, keeps its meta.json, and transcribes on the next start. The 113 is why must_succeed: false is required rather than tidy.

Verification

Against the real launchd job on an M4, running the exact command the postflight will run:

under launchd, pid=9893
launchctl exit=0
old pid=9893  new pid=9921        → daemon replaced
plist after: 1786729370 …/com.terrifiedbug.yap.plist   → mtime unchanged
still a launchd job: yes           → launchctl print resolves
"LastExitStatus" = 0;              → the old daemon exited gracefully

That covers all three acceptance criteria in the issue. The generated cask was also checked as the workflow produces it — heredoc extracted from release.yml, expanded with a real VERSION/SHA256, then ruby -c (Syntax OK) and brew style --cask inside a throwaway tap (no offenses; it caught a Layout/HashAlignment violation on the first pass, hence the table alignment above).

Full local CI gate green: swift test, release build, the three smoke subcommands, embedded __info_plist, and the workflow's own version: "0.1.4" guard.

Deliberately not included

The issue's closing note — a yap doctor line comparing the running image against the executable on disk, for people who installed from the .dmg and still restart by hand. It is outside the acceptance criteria and needs a heuristic (process start time against binary mtime) with its own false-positive modes, so it wants to be its own change rather than a rider on a release.

brew upgrade --cask yap replaces /Applications/yap.app, but a running
process keeps the image it already mapped, so you stayed on the previous
version until something restarted the daemon. `yap --version` reads the new
binary on disk and agreed with what you just installed, which is the worst
shape for a bug: it looked fixed.

postflight, not an uninstall stanza. Homebrew runs uninstall directives on
upgrade too, where `launchctl` would delete the LaunchAgent plist and switch
launch-at-login off, and `quit` would be followed by `open -b` — starting the
daemon outside launchd where neither `yap stop` nor the login item can reach
it. kickstart -k replaces the job launchd already owns, so the plist and the
login item are untouched.

Measured rather than assumed, because the alternative is losing a meeting:
-k delivers SIGTERM, not SIGKILL. yap already catches SIGTERM and routes it
through applicationWillTerminate, so a recording in flight is finalized —
LastExitStatus 0 — and transcribes on the next start instead of losing its
meta.json. launchd brings the new image up about five seconds later.

must_succeed: false because launchctl exits 113 when there is no such job,
and an install that never asked for launch-at-login must not fail its
upgrade over it.

Closes #6
@TerrifiedBug
TerrifiedBug merged commit 83f6243 into main Aug 19, 2026
1 check passed
@TerrifiedBug
TerrifiedBug deleted the fix/cask-restarts-daemon branch August 19, 2026 18:20
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.

brew upgrade leaves the previous daemon running

1 participant