#16085: Signal: add REST API support for containerized deployments
channel: signal
size: XL
Cluster:
Slack and Signal Enhancements
> **AI-assisted**: Built with Claude (Copilot CLI). Fully tested. Human reviewed and verified.
## Summary
- **Problem:** Signal channel only works with direct signal-cli (JSON-RPC + SSE). The official `bbernhard/signal-cli-rest-api` Docker image uses REST + WebSocket endpoints that don't exist in the current implementation, causing 404 errors and failed connections.
- **Why it matters:** Containerized Signal deployments are broken — users must install signal-cli directly on the host (requires Java, complex setup).
- **What changed:** Added a unified adapter (`client-adapter.ts`) with a new container client (`client-container.ts`) that routes between native JSON-RPC and container REST modes. Includes auto-detection, `channels.signal.apiMode` config, WebSocket event streaming, and base64 attachment encoding for the container `/v2/send` endpoint.
- **What did NOT change (scope boundary):** Native signal-cli mode is unaffected. Inbound attachment handling is unchanged.
## Change Type (select all)
- [x] Bug fix
- [ ] Feature
- [x] Refactor
- [ ] Docs
- [ ] Security hardening
- [ ] Chore/infra
## Scope (select all touched areas)
- [ ] Gateway / orchestration
- [ ] Skills / tool execution
- [ ] Auth / tokens
- [ ] Memory / storage
- [x] Integrations
- [ ] API / contracts
- [ ] UI / DX
- [ ] CI/CD / infra
## Linked Issue/PR
- Closes #10240
## User-visible / Behavior Changes
- Signal now works with `bbernhard/signal-cli-rest-api` Docker containers out of the box.
- New `channels.signal.apiMode` config option (`"auto"` | `"native"` | `"container"`). Default `"auto"` auto-detects the mode.
- Container mode uses REST (`/v2/send`) + WebSocket (`/v1/receive/{account}`) instead of JSON-RPC + SSE.
## Security Impact (required)
- New permissions/capabilities? `No`
- Secrets/tokens handling changed? `No`
- New/changed network calls? `Yes` — container mode uses different endpoints (`/v2/send`, `/v1/receive`, `/v1/about`, `/v1/typing-indicator`, `/v1/receipts`, `/v1/reactions`) on the same configured `httpUrl`
- Command/tool execution surface changed? `No`
- Data access scope changed? `No`
- Risk + mitigation: Same trust boundary as native mode — all calls go to the user-configured `httpUrl`. No new external network access.
## Repro + Verification
### Environment
- OS: Linux
- Runtime/container: Node 22 + bbernhard/signal-cli-rest-api container
- Integration/channel: Signal (container mode)
### Steps
1. Run `bbernhard/signal-cli-rest-api` container
2. Configure `channels.signal.httpUrl` pointing to the container
3. Start gateway — auto-detection picks container mode
4. Send/receive messages via Signal
### Expected
- Messages flow bidirectionally via WebSocket + REST
### Actual (before)
- 404 errors on `/api/v1/events` and `/api/v1/rpc`, reconnect loop
## Evidence
- [x] Failing test/log before + passing after
- 186 Signal tests pass (13 test files), including adapter, container, and native client tests
- `pnpm build && pnpm check && pnpm test` all pass
## Human Verification (required)
- Verified scenarios: `pnpm build`, `pnpm tsgo` (0 errors), `pnpm check` (0 errors), `pnpm test src/signal` (186 tests pass)
- Edge cases checked: username-based targets, group ID formatting, cache TTL expiry, MIME detection fallback, empty attachments
- What you did **not** verify: Live containerized deployment end-to-end
## Compatibility / Migration
- Backward compatible? `Yes`
- Config/env changes? `Yes` — new optional `channels.signal.apiMode` field (default `"auto"`, no action needed)
- Migration needed? `No`
## Failure Recovery (if this breaks)
- How to disable/revert this change quickly: Set `channels.signal.apiMode: "native"` to force legacy mode
- Files/config to restore: `src/signal/client-container.ts`, `src/signal/client-adapter.ts`
- Known bad symptoms: Signal messages not delivered when using container mode
## Risks and Mitigations
- Risk: Auto-detection probes wrong mode if both native and container endpoints are reachable
- Mitigation: 30s TTL cache; users can force mode via `apiMode: "native"` or `"container"`
- Risk: Large attachments cause memory pressure during base64 encoding
- Mitigation: Same behavior as other channels; typical attachments are small
Most Similar PRs
#19398: feat(signal): support native signal-cli JSON-RPC WebSocket
by jxstanford · 2026-02-17
70.7%
#15956: feat(signal): enhanced inbound message handling
by heyhudson · 2026-02-14
66.1%
#16704: Signal: reduce REST lock contention in send/receive loop
by hashpuppy · 2026-02-15
64.1%
#20732: fix(signal): forward replyTo as quoteTimestamp/quoteAuthor for nati...
by arjunblj · 2026-02-19
63.3%
#12984: fix(signal): fall back to JSON-RPC for health check on signal-cli 0...
by omair445 · 2026-02-10
63.2%
#19807: fix: apply #19779 Docker/TS strict-build fixes
by dalefrieswthat · 2026-02-18
61.1%
#8271: feat(signal): Add full quoted message context support
by ProofOfReach · 2026-02-03
60.6%
#19636: fix(agents): harden overflow recovery observability + subagent term...
by Jackten · 2026-02-18
60.5%
#18170: feat(telegram): support local Bot API server via `apiRoot` config
by iemesowum · 2026-02-16
59.9%
#20947: feat: expand Copilot model catalog with anthropic-messages API
by austenstone · 2026-02-19
59.4%