Skip to content

fix(imsg-react): tapbacks on a macOS 27 gateway - #138

Merged
nixfred merged 1 commit into
nixfred:mainfrom
ianswope:fix/tapbacks-macos27
Oct 8, 2026
Merged

nixfred merged 1 commit into
nixfred:mainfrom
ianswope:fix/tapbacks-macos27

Conversation

@ianswope

@ianswope ianswope commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

On a macOS 27.0.1 gateway every outgoing tapback failed. Four causes, each measured on the Mac:

  1. Consent check. system_events_consent reads the per-user TCC.db, which macOS 27 no longer has (the folder is gone), so it always answered "cannot confirm the Automation grant" even though imsg-read's System Events calls succeed. With no file it now asks macOS via AEDeterminePermissionToAutomateTarget(..., askUserIfNeeded=false), which answers and never prompts (System Events is launched hidden through LaunchServices first; the API answers -600 for a target that isn't running). A TCC.db that exists still decides, unchanged.
  2. No tapback actions on bubbles. A 27 bubble offers Tapback Details…, Reply…, Attach Sticker…, Copy, Share…, Delete…, none of the six. The bubble search therefore found nothing and could not even page (no-bubbles: it pages with a live bubble's actions). On 27, imsg-react uses Edit ▸ Tapback Last Message… instead, which opens the Tapback Picker on the conversation's newest message with buttons named Heart, Thumbs up, Thumbs down, Ha ha!, Exclamation mark, Question mark. The picker labels its message (Message, <text>); nothing is pressed unless that matches the target. chat.db still decides success.
    Neither an AX press, a CGEvent click nor a System Events click at selects an older bubble (the item stays "Tapback Last Message…"), and arrow keys don't move the picker, so on 27 only the newest message can take a tapback. An older one is refused up front (not-last, exit 65) without touching Messages.
  3. Blocking click. A System Events click on that menu item can block until the picker closes (seen by index and by name), so it is sent from AppleScript under ignoring application responses. The button is pressed by its action name, AXPress (press is only the description).
  4. Multi-line labels. parseLabel used ., so a bubble whose text spans lines never matched (not-rendered). Also affects 26.

Each run appends <time> <code> exit=<n> to ~/.blip/react.log on the Mac (no GUID or text, rotated at 64 KB), which is how the last two causes were found. README's tapbacks section gains a paragraph on 27.

Verified

  • A thumbs-up on the newest message of a 1:1 on a 27.0.1 gateway landed in chat.db (type 2001, 16.9 s), react.log: added exit=0.
  • test_react.py: 60 pass (new: no-TCC.db consent answers, picker route, not-last refusal before any UI, picker on another message presses nothing, failed menu click presses nothing, 27 skips the bubble search, 26 path unchanged). bun test: 750 pass.

Not addressed

On the failed runs no error reached Blip's status line, although imsg-react exited non-zero with a reason on stderr. Not investigated here.

On macOS 27.0.1 every tapback failed. Four things, measured on the Mac:

- The consent check read ~/Library/Application Support/com.apple.TCC/TCC.db,
  which 27 no longer has, so it always refused ("cannot confirm the
  Automation grant"). With no file, it now asks macOS through
  AEDeterminePermissionToAutomateTarget with askUserIfNeeded=false, which
  answers and never prompts. A TCC.db that exists still decides.
- Bubbles no longer carry the six tapback actions (they offer Tapback
  Details…, Reply…, Copy...), so the bubble search found nothing to press and
  could not page (it pages with a live bubble's actions). On 27 it now uses
  Edit ▸ Tapback Last Message…, which opens the Tapback Picker on the
  conversation's newest message with the six as buttons of the same names.
  The picker names its message; nothing is pressed unless that is the
  target's text. chat.db still decides success. No click or AX press
  selects an older bubble, so an older message is refused (not-last, 65).
- A System Events click on that menu item can block until the picker
  closes; it is sent from AppleScript under `ignoring application
  responses`. The button is pressed by its action name, AXPress.
- A bubble label spanning lines never parsed (`.` in the label regex).

Each run appends its outcome code and exit status to ~/.blip/react.log on
the Mac (no GUID, no text; rotated at 64 KB).

Verified: a thumbs-up on the newest message of a 1:1 landed in chat.db
(type 2001, 16.9 s). test_react.py 60 pass, bun test 750 pass.
@ianswope
ianswope requested a review from nixfred as a code owner October 8, 2026 02:15
@nixfred
nixfred merged commit fef6981 into nixfred:main Oct 8, 2026
1 check passed
@nixfred

nixfred commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Merged, thank you Ian. This is the one that matters for me personally: my gateway is on macOS 27.2 with no per-user TCC.db, so the old consent check could never pass and tapbacks were dead here. On my Mac the new check now answers macOS 27, granted, and nothing prompted. The safety story is right too: the picker is pressed only when its own "Message, " label matches the target, an older message is refused before Messages is touched, and the probe asks with askUserIfNeeded=false. 750 tests, 60 react tests and every bridge test green; installed on my Mac.

Your "not addressed" note (a non-zero imsg-react with a reason on stderr never reaches Blip's status line) is worth an issue of its own if you have a minute; I have not looked at it yet.

nixfred added a commit that referenced this pull request Oct 8, 2026
LR-T: blip, change-log, weekly-notes, #138, tapbacks, macos-27, ianswope
LR-D: blip

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BeRMdPWNC2myz9U8hEeeCi
@ianswope

ianswope commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Thanks Fred, and thanks for Blip itself. I use it all day and really love it, so it's a pleasure to help where I can. Glad this one brought tapbacks back on your 27.2 gateway too.

On the status-line note: I found the cause, so I'll send a PR instead of an issue. The imsg-react run lived in the view that started it, and the popout and the window are both destroyed on close. On a 27 gateway a run takes 15 to 75 seconds, so closing Blip mid-run took the run and its failure with it. The fix moves the run into BarWidget and holds a failure per conversation until a view showing it picks it up. It's deployed here; I'm confirming it on a real failure before I open the PR.

nixfred added a commit that referenced this pull request Oct 8, 2026
WHAT: the imsg-react Process moves from BlipView to BarWidget (runTapback,
reactProc; one run at a time), which outlives the popout and the window.
Every exit emits tapbackExited(chat, ok); a failure waits in
tapbackFailures[chat] (memory only, reason text, no message content) until
a view showing that conversation takes it, now or when it is next opened.
The not-started guard (sawExit) moves with it, same pattern. imsg-react on
macOS 27 runs the newest-message check before Messages is touched, so an
older target is refused in about a second without raising Messages. From
Ian Swope (github.com/ianswope, X @ianfs), the 'not addressed' note on #138.
WHY: a run takes 15-75 s on a 27 gateway; closing Blip mid-run destroyed
the view's Process, and its stderr reason never reached the status line.
VERIFIED: merged with main, bun 750 pass, test_react.py OK, every bridge
test. door one-way; Fred approved 2026-10-07.

LR-T: blip, tapbacks, imsg-react, barwidget, process-lifetime, macos-27, ianswope
LR-D: blip
LR-R: #139

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BeRMdPWNC2myz9U8hEeeCi
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.

2 participants