Skip to content

fix(bluebubbles): use 127.0.0.1 in webhook URL + migrate stale localhost registrations - #69593

Open
fluxkapacitor wants to merge 1 commit into
NousResearch:mainfrom
fluxkapacitor:fix/bluebubbles-ipv4-webhook-migration
Open

fix(bluebubbles): use 127.0.0.1 in webhook URL + migrate stale localhost registrations#69593
fluxkapacitor wants to merge 1 commit into
NousResearch:mainfrom
fluxkapacitor:fix/bluebubbles-ipv4-webhook-migration

Conversation

@fluxkapacitor

@fluxkapacitor fluxkapacitor commented Jul 22, 2026

Copy link
Copy Markdown

What does this PR do?

Fixes the BlueBubbles inbound webhook silently failing on macOS, and migrates stale localhost registrations left by prior Hermes versions.

The bug

The adapter registered its inbound webhook as http://localhost:<port>/..., but the aiohttp listener binds IPv4 127.0.0.1 only. On macOS, localhost resolves to IPv6 ::1 first, so BlueBubbles (Node.js) POSTs the webhook to ::1 and the delivery is silently dropped — no error surfaces, but Hermes never receives inbound messages.

The fix

Normalise all loopback/wildcard hosts (0.0.0.0, 127.0.0.1, localhost, ::) to the explicit IPv4 literal 127.0.0.1 so the registered URL matches the address the listener actually binds.

The migration (addresses hermes-sweeper feedback on #8263)

Existing installations that already have a localhost-based webhook registered with BlueBubbles won't get cleaned up by _unregister_webhook — it only removes the current URL. The stale localhost registration survives as a dead entry, and BlueBubbles may still attempt to deliver to the broken URL.

This PR adds:

  • _legacy_webhook_urls property: computes the pre-fix localhost variant of the current URL
  • Migration logic in _register_webhook: before creating the new registration, finds and removes any stale localhost-based registrations left by earlier versions
  • Tests for both the migration path and the no-legacy-needed path

This is the same core fix as #8263, but with the sweeper-requested upgrade-path migration included.

Related Issue

Fixes #8512 (BlueBubbles webhook fails on macOS due to IPv6 localhost resolution).
Covers #45308.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • gateway/platforms/bluebubbles.py:
    • _webhook_url: normalises loopback hosts to 127.0.0.1 instead of localhost
    • _legacy_webhook_urls: new property returning pre-fix URL variants for migration
    • _register_webhook: removes stale localhost registrations before creating the new one
  • tests/gateway/test_bluebubbles.py:
    • Updated TestBlueBubblesWebhookUrl to assert IPv4 normalisation
    • Added test_legacy_webhook_urls_returns_localhost_variant
    • Added test_legacy_webhook_urls_empty_when_custom_host
    • Added test_register_migrates_legacy_localhost_webhook
    • Added test_register_skips_migration_when_no_legacy

How to Test

  1. On macOS, configure BlueBubbles + Hermes messaging; send an iMessage to the Mac.
  2. Before fix: message is visible in BlueBubbles but Hermes never responds (webhook POSTs to ::1, refused by the IPv4-only listener).
  3. After fix: curl -s -o /dev/null -w '%{http_code}' http://[::1]:<port>/...000 (connection refused, confirms IPv6 path dead) and curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:<port>/...200. Inbound iMessage now reaches Hermes and it replies.
  4. Migration test: Upgrade from a pre-fix version that left a localhost registration. Start the new gateway — the stale registration is automatically removed and replaced with the 127.0.0.1 one.
  5. pytest tests/gateway/test_bluebubbles.py -q → 64 passed.

Checklist

Note for reviewers

This incorporates the core fix from #8263 (normalise to 127.0.0.1) plus the stale-localhost migration that the hermes-sweeper flagged as a blocker on that PR. An alternative approach is to make the listener dual-stack (bind None) and keep localhost; this minimal change keeps the IPv4 listener and pins the URL, which is lower risk. Happy to switch to dual-stack if preferred.

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 22, 2026
@fluxkapacitor
fluxkapacitor force-pushed the fix/bluebubbles-ipv4-webhook-migration branch from a6c7a03 to ae4538d Compare July 23, 2026 16:51

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the default IPv4/localhost mismatch; current main still has that mismatch: gateway/platforms/bluebubbles.py:284 binds self.webhook_host, while :315-318 registers localhost for the default 127.0.0.1 configuration.

Problems

  • The changed host set includes ::, but the listener still binds the configured host at gateway/platforms/bluebubbles.py:284. Rewriting that IPv6 bind to 127.0.0.1 registers an address with no corresponding IPv4 listener. The changed parameterized test accepts this broken mapping rather than exercising the bind/advertise contract.
  • The migration only queries localhost?...password=<current password>. Commit 326cbbe40ea introduced password-bearing registrations after prior releases used bare URLs; exact matching in gateway/platforms/bluebubbles.py:343-349 leaves those older stale registrations untouched.

Suggested changes

  • Keep registration address family consistent with the TCPSite bind, including ::.
  • Migrate both bare and password-bearing historical localhost registrations, with a regression test for the pre-326cbbe40ea form.

Automated hermes-sweeper review.

Comment thread gateway/platforms/bluebubbles.py Outdated
@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 30, 2026
…ost registrations

On macOS, Node.js resolves 'localhost' to IPv6 ::1 first, but the
aiohttp webhook listener binds IPv4 127.0.0.1 only. Normalise all
loopback hosts to the explicit IPv4 literal so webhook deliveries
succeed instead of silently failing with ECONNREFUSED ::1.

Also adds automatic migration of stale 'localhost'-based registrations
left by prior Hermes versions. Without cleanup, the old registration
persists as a dead entry because _unregister_webhook only removes the
current URL. The new _register_webhook detects and removes legacy
localhost registrations before creating the new one, providing a
seamless upgrade path.

Fixes NousResearch#8512, covers NousResearch#45308.
@fluxkapacitor

Copy link
Copy Markdown
Author

Thanks for the review — both points are addressed and the branch is rebased onto current main.

1. Bind / advertise address-family consistency

_webhook_url no longer maps ::127.0.0.1.

  • IPv4 bind hosts (0.0.0.0, 127.0.0.1, localhost) → advertise 127.0.0.1 (macOS / Node localhost::1 fix)
  • IPv6 any-address binds (::, ::0, [::]) → advertise [::1] so registration matches the TCPSite family
  • Custom hosts unchanged

Added test_ipv6_any_bind_advertises_loopback_v6 so the broken IPv6→IPv4 mapping can’t regress.

2. Legacy localhost migration (bare + password-bearing)

Migration now always tries the bare http://localhost:… URL (pre-326cbbe form), and also the password-bearing form when a password is configured.

Added test_register_migrates_bare_legacy_localhost_without_password_query for the pre-password registration shape.

Verify

pytest tests/gateway/test_bluebubbles.py -q → green on this branch.

@fluxkapacitor
fluxkapacitor force-pushed the fix/bluebubbles-ipv4-webhook-migration branch from ae4538d to 312d394 Compare July 30, 2026 10:33
@fluxkapacitor

Copy link
Copy Markdown
Author

Friendly ping — it's been about a week since this was rebased and the review feedback was addressed (bind/advertise address-family consistency + bare/password-bearing legacy localhost migration, both with regression tests). The branch is still mergeable against current main.

This one is a real daily-driver bug for macOS BlueBubbles users: without it, inbound webhooks silently fail whenever the listener binds IPv4 but the registered URL uses localhost (which Node resolves to ::1 first). No rush, but a re-review when someone has a moment would be appreciated. Happy to make any further changes.

@fluxkapacitor

Copy link
Copy Markdown
Author

Additional data point on why the IPv4 literal is required on macOS — tested 2026-08-07 on macOS (Python 3.11.15, aiohttp 3.14.1):

  1. Binding the webhook listener to ::1 (IPv6 loopback) on port 8645: the TCPSite starts without error, but inbound connections to http://[::1]:8645/ time out.
  2. Same result on a fresh port (no interaction with the running gateway) — rules out port sharing.
  3. A plain socket server on ::1 on the same machine accepts connections fine (HTTP round-trip passes), and the macOS Application Firewall is disabled — so the OS/IPv6 stack is healthy.
  4. The failure is specific to aiohttp serving IPv6 loopback on this Python build.

Practical consequence: the docs' default behavior (register localhost, which Node resolves to ::1 first on macOS) is unreachable, AND BLUEBUBBLES_WEBHOOK_HOST=::1 is not a viable workaround on at least this configuration — the IPv4 literal (127.0.0.1) is required for inbound webhooks to work. This strengthens the case for keeping the advertised URL consistent with an IPv4 bind as this PR does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: BlueBubbles webhook fails on macOS due to IPv6 localhost resolution

3 participants