fix(panel): refresh a stale hook script so handoffs record - #144
Merged
Conversation
StuBehan
force-pushed
the
fix/notify-script-drift
branch
from
July 29, 2026 18:13
ab9d060 to
3f6f547
Compare
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.
A user reported an empty Tickets tab.
~/.stack-nudge/handoffs.jsonldidn't exist, socaptureHandoffhad never completed once, andgrep -c claude_session_id ~/.stack-nudge/notify.shreturned 0.Their installed hook script predated v1.12.0, the release that added
claude_session_idto the socket payload, while the app was on 1.25.3.notify.shis half of the wire protocol with the panel, but it lives in~/.stack-nudge/and is only written byinstall.shor the first-launch wizard, which is skipped once the file exists. Updates swap the.appalone. So an install that has only ever self-updated keeps whatever script version first arrived, forever, and every Stop event hits a silentreturn. Notifications keep working, so only the Tickets tab looks broken.Changes
The payload no longer depends on jq.
session_id/transcript_pathwere extracted with jq and degraded to empty strings when it was absent, which is indistinguishable from "no session". They're now parsed by the python3 block that already builds and sends the payload. The hook JSON reaches it via env, capped at 32 KiB: env and argv share ARG_MAX, and aPermissionRequesttool_inputcan carry a whole file, which would fail the exec and lose the event.The installed script self-heals.
notify.shcarries# stack-nudge-version: <x.y.z>, registered in.release-please-config.jsonextra-filesbesidepanel/Info.plist.Bootstrap.refreshNotifyScriptIfNeeded()runs fromapplicationDidFinishLaunchingand rewrites the installed copy when its stamp differs from the bundled one. Stamp comparison rather than content hashing, so a script deliberately tweaked at the current version survives and a version bump is the overwrite signal.The swap is temp file +
replaceItemAt, because agents are live while the app runs and a remove-then-copy window would leave a hook with no script to invoke;install.shdoes the same withmv -f.chmodhappens after the replace:replaceItemAtdeliberately carries the original file's attributes onto the replacement, so a copy that arrived 0644 would otherwise stay non-executable and every hook would fail with "permission denied". A test pins that.The write path is observable.
captureHandoff's silent returns now count byHandoffDropReason(missing session id / missing cwd / not a git repo) and log a line toapp.log, except the not-a-repo case, which is an expected skip.OutcomesView.emptyReasonpicks the empty state from that state instead of always claiming no sessions: filtered by hide-shipped, all dropped with the remedy, or genuinely idle.Surfacing. Settings' about footer warns when a stamp mismatch survives the launch repair, which means the rewrite itself failed (read-only dotdir, wrong owner).
install.shnotes a missing jq as optional, now that only the permission-banner detail uses it. README documents the empty-tab triage and the refresh.Verification
main:claude_session_id: <MISSING>; this branch: present@mainharnessmake typecheck-tests(27 files)./build.sh,bash -n,shellcheck --severity=warningswift testneeds XCTest (Xcode-only), so the assertions were run through an in-module harness locally and CI is the authority on the XCTest suite. The shim gainedXCTAssertThrowsErrorandaddTeardownBlock.Not verified live: the launch-time refresh inside a running bundle, which needs
make reloadto replace the installed app.Out of scope
No backfill: nothing was ever recorded for affected users, and reconstructing a ledger from transcripts is separate work.
permission_context()still uses jq.