Skip to content

feat(signal): add read receipt support#3705

Open
gibbsoft wants to merge 1 commit intoNousResearch:mainfrom
gibbsoft:feat/signal-read-receipts
Open

feat(signal): add read receipt support#3705
gibbsoft wants to merge 1 commit intoNousResearch:mainfrom
gibbsoft:feat/signal-read-receipts

Conversation

@gibbsoft
Copy link
Copy Markdown

Summary

  • Send read receipts after handling inbound Signal messages
  • Controlled by SIGNAL_SEND_READ_RECEIPTS env var (defaults to true) or send_read_receipts in signal platform config

Motivation

Signal shows messages as unread/undelivered when the recipient doesn't send read receipts. This makes hermes-agent behave like a proper Signal client.

Test plan

  • Send a message to hermes via Signal — verify blue double-tick (read) appears on sender's device
  • Set SIGNAL_SEND_READ_RECEIPTS=false — verify no read receipts are sent

Send read receipts after handling inbound Signal messages. Controlled
by SIGNAL_SEND_READ_RECEIPTS env var (defaults to true) or
send_read_receipts in signal platform config.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds optional outbound Signal read receipts so Hermes behaves more like a native Signal client, controlled via configuration/env.

Changes:

  • Add send_read_receipts option to Signal adapter and send a read receipt after processing inbound messages.
  • Add SIGNAL_SEND_READ_RECEIPTS env override (defaulting to true) into Signal platform extra config.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
gateway/platforms/signal.py Adds adapter config flag and implements sendReceipt call after handling inbound messages.
gateway/config.py Adds env override plumbing for SIGNAL_SEND_READ_RECEIPTS into the Signal platform config.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

"http_url": signal_url,
"account": signal_account,
"ignore_stories": os.getenv("SIGNAL_IGNORE_STORIES", "true").lower() in ("true", "1", "yes"),
"send_read_receipts": os.getenv("SIGNAL_SEND_READ_RECEIPTS", "true").lower() in ("true", "1", "yes"),
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

Env override adds SIGNAL_SEND_READ_RECEIPTS, but there’s no test asserting the default (true) or the false case in tests/gateway/test_signal.py::TestSignalConfigLoading.test_apply_env_overrides_signal. Please add assertions to prevent regressions (e.g., default enabled when unset, disabled when set to "false").

Suggested change
"send_read_receipts": os.getenv("SIGNAL_SEND_READ_RECEIPTS", "true").lower() in ("true", "1", "yes"),
"send_read_receipts": _coerce_bool(os.getenv("SIGNAL_SEND_READ_RECEIPTS"), default=True),

Copilot uses AI. Check for mistakes.
self.http_url = extra.get("http_url", "http://127.0.0.1:8080").rstrip("/")
self.account = extra.get("account", "")
self.ignore_stories = extra.get("ignore_stories", True)
self.send_read_receipts = extra.get("send_read_receipts", False)
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

send_read_receipts defaults to False here, but the PR description (and the env override in gateway/config.py) indicate the default should be True. As written, read receipts will be disabled by default when Signal is configured via file config (no env override), which is inconsistent with the intended behavior. Consider changing the fallback default to True (or otherwise make the default consistent across env + config).

Copilot uses AI. Check for mistakes.
Comment on lines +517 to +518
# Send read receipt if enabled
if self.send_read_receipts and sender and ts_ms:
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

This will also send a read receipt for "Note to Self" messages (is_note_to_self=True) where sender is the adapter's own account (self-message filtering is explicitly bypassed for that case). Sending read receipts to the local account is unnecessary and may cause unexpected RPC errors/noise. Consider adding a guard (e.g., skip when is_note_to_self or when sender == self._account_normalized).

Suggested change
# Send read receipt if enabled
if self.send_read_receipts and sender and ts_ms:
# Send read receipt if enabled (but skip self / note-to-self messages)
if (
self.send_read_receipts
and sender
and ts_ms
and not is_note_to_self
and sender != getattr(self, "_account_normalized", None)
):

Copilot uses AI. Check for mistakes.
Comment on lines +517 to +519
# Send read receipt if enabled
if self.send_read_receipts and sender and ts_ms:
await self._send_read_receipt(sender, ts_ms)
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

New behavior (sending read receipts after handling inbound messages) isn’t covered by the existing Signal adapter test suite (tests/gateway/test_signal.py). Please add tests that verify _rpc("sendReceipt", ...) is called when send_read_receipts is enabled, and not called when disabled (and for Note-to-Self if that’s intentionally excluded).

Copilot uses AI. Check for mistakes.
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