Skip to content

fix(homekit): only hang up when the last HomeKit session ends - #89

Open
atdr wants to merge 1 commit into
feat/hangup-grace-periodfrom
fix/hangup-only-on-last-session
Open

fix(homekit): only hang up when the last HomeKit session ends#89
atdr wants to merge 1 commit into
feat/hangup-grace-periodfrom
fix/hangup-only-on-last-session

Conversation

@atdr

@atdr atdr commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • The Twilio call was hung up whenever any HomeKit streaming session stopped, not only the last one. A call in progress could be ended out from under a live stream.
  • HomeKit runs up to cameraStreamCount sessions at once and starts a second one when you move between the room view's camera tile and the full camera view, so this fires in ordinary use rather than in some corner case.
  • One condition: schedule the hangup only once activeSessions is empty.

Stacked on #88

This sits on top of feat/hangup-grace-period. The bug predates that PR, but the grace window is what made it legible, so the two read best in order. Reviewing this diff alone shows the one-line condition plus its tests.

What was actually happening

Caught on the production Pi running 2.2.0-beta.2:

12:09:19.866  mulaw-stream-bound   sessionId d332e46a   <- session A, streaming
12:09:23.345  mulaw-stream-bound   sessionId ac5c8c21   <- session B starts
12:09:24.157  hangup-scheduled     sessionId ac5c8c21   <- B stops, schedules a hangup
12:09:27.158  Hanging up call                           <- call dies, A still streaming

Session B stopping ended the call three seconds into session A's stream. From the phone it looked like the stream simply died shortly after opening.

This is not a regression from #88. Before the grace window the same STOP called hangUpCall immediately, so the call died the instant the second session ended, with nothing in the journal to say why. #88 delayed it by three seconds and gave it a name, which is how it was found at all. The hangup-scheduled line naming a sessionId that never bound a mulaw stream is what gave it away.

Changes

homekit.js

-if (hangUp && activeCall) {
+if (hangUp && activeCall && activeSessions.size === 0) {

_stopSession deletes its own session from the map first, so an empty map means this was the last one and the user is genuinely finished. The comment records the observed session IDs so the next reader has the evidence rather than the assertion.

tests/homekit-hangup-grace.test.cjs

Four cases driving _stopSession rather than scheduleHangUp:

  • a stop with another session still streaming does not end the call, and leaves the surviving session in place
  • the last session stopping does end the call
  • stopping every session in turn ends the call exactly once
  • endHapSession (the caller-hung-up path) never hangs up, whatever is streaming

_stopSession and activeSessions are exported to make this reachable. Standing up two real SRTP sessions would test the HAP stack rather than this condition.

Why the existing tests missed it

Worth stating, because it is the reusable lesson. #88's cases all drove scheduleHangUp directly. That covers when the timer fires, and the fault was entirely in whether it should have been set at all. A test that starts one layer above the bug cannot see it, however thorough it is about everything below.

The new cases enter through _stopSession, which is where the decision lives. Reverting the condition to if (hangUp && activeCall) fails the multi-session case and passes the other eleven.

Test Plan

  • All five gates pass locally (typecheck, lint, format:check, check, test — 146 tests).
  • npm run coverage passes; homekit.js rises to 42.26% lines and the project to 69.09%.
  • Reverting the session check fails exactly the new multi-session case (11 pass, 1 fail) and restoring it passes all 12, so the test tracks this bug specifically rather than the area around it.
  • 2.2.0-beta.3 published from this commit and deployed to the Pi: pairedClients 4 before and after, probes green, activeSessions.size === 0 confirmed present in the installed code.
  • Live call: open the camera view, move back out to the room view, and confirm the stream survives the second session ending. Expect no hangup-scheduled at that point, and exactly one when the last session goes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rq7wnggNTrhwGMADfJ5yvf

The stop handler ended the Twilio call whenever any streaming session
stopped. HomeKit runs up to cameraStreamCount sessions at once and
starts a second one when you move between the room view's camera tile
and the full camera view, so a STOP routinely arrives while another
session is still streaming.

Observed on the production Pi: session ac5c8c21 stopping ended the call
under session d332e46a, three seconds into a live stream. The call now
ends only once activeSessions is empty.

This predates the grace window, which merely delayed the same hangup by
three seconds and made it legible in the journal. Before that it fired
instantly and looked like the stream simply dying.

tests/homekit-hangup-grace.test.cjs gains four cases driving
_stopSession rather than scheduleHangUp. The existing cases could not
have caught this: they cover when the timer fires, not whether it should
have been set, and the fault was entirely in the latter. Reverting the
session check fails the new multi-session case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rq7wnggNTrhwGMADfJ5yvf
@atdr
atdr added this pull request to stack #90 September 12, 2026 15:19
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.

1 participant