#22335: fix: harden normalizeE164 against empty/invalid inputs returning bare '+'
size: S
Cluster:
WhatsApp and Google Chat Fixes
## Summary
- Problem: `normalizeE164` returns a bare `"+"` for invalid inputs (empty string, whitespace, non-digit strings, bare `whatsapp:` prefix), which is not a valid E.164 number.
- Why it matters: Downstream functions silently treat `"+"` as valid, causing incorrect matches in `isSelfChatMode` (two invalid numbers both normalize to `"+"` and match), invalid JIDs from `toWhatsappJid` (`"@s.whatsapp.net"`), and bypassed guards in group gating (`if (!sender)` fails because `"+"` is truthy).
- What changed: `normalizeE164` now returns an empty string `""` when no digits are present; `isSelfChatMode` guards against empty normalized results; `toWhatsappJid` falls back to the raw input when normalization yields empty. Added 12 edge case tests.
- What did NOT change (scope boundary): Return type stays `string` (not `string | null`); all existing callers remain compatible.
## Change Type (select all)
- [x] Bug fix
## Scope (select all touched areas)
- [x] Integrations
## Linked Issue/PR
- Related: WhatsApp/Signal phone number normalization
## User-visible / Behavior Changes
- Invalid phone number inputs no longer silently produce `"+"` — they now produce `""` which is correctly handled as invalid by downstream logic.
- `isSelfChatMode` no longer incorrectly returns `true` when both self and allowFrom entries have no digits.
## Security Impact (required)
- New permissions/capabilities? No
- Secrets/tokens handling changed? No
- New/changed network calls? No
- Command/tool execution surface changed? No
- Data access scope changed? No
## Repro + Verification
### Environment
- OS: Windows 10
- Runtime/container: Node 22+
- Integration/channel: WhatsApp, Signal
### Steps
1. Call `normalizeE164("")` — previously returned `"+"`, now returns `""`
2. Call `isSelfChatMode("abc", ["def"])` — previously returned `true`, now returns `false`
3. Call `toWhatsappJid("")` — previously returned `"@s.whatsapp.net"`, now returns `""`
### Expected
- Empty/invalid inputs should not produce `"+"`
### Actual (before fix)
- `normalizeE164("")` returned `"+"`
## Evidence
- [x] Failing test/log before + passing after: 38 tests pass, including 12 new edge case tests
## Human Verification (required)
- Verified scenarios: All 38 unit tests pass; also ran WhatsApp normalize tests (8 pass)
- Edge cases checked: empty string, whitespace, non-digit strings, bare `"+"`, bare `"whatsapp:"`, valid numbers with/without prefix
- What you did **not** verify: Live WhatsApp/Signal integration test
## Compatibility / Migration
- Backward compatible? Yes
- Config/env changes? No
- Migration needed? No
## Failure Recovery (if this breaks)
- How to disable/revert this change quickly: Revert the single commit
- Files/config to restore: `src/utils.ts`
- Known bad symptoms: Phone normalization returning empty string where `"+"` was expected
## Risks and Mitigations
- Risk: Callers that compared `normalizeE164(x) === "+"` would break — but no such callers exist (verified via grep).
- Mitigation: Searched all 22 call sites; all handle empty string correctly.
## AI-assisted PR
- [x] Mark as AI-assisted
- Testing: Fully tested (38 unit tests pass)
- Confirmed understanding of all code changes
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Hardens `normalizeE164` against empty/invalid inputs that previously returned a bare `"+"`, which is not a valid E.164 number. Now returns `""` for invalid inputs, with corresponding guards added in `isSelfChatMode` and `toWhatsappJid`.
- **`normalizeE164`**: Extracts pure digits before prepending `+`; returns `""` when no digits are present instead of `"+"`.
- **`isSelfChatMode`**: Guards against empty normalized self-number to prevent false `true` when two invalid inputs both normalize to the same value.
- **`toWhatsappJid`**: Falls back to the raw input when normalization yields empty, avoiding invalid JIDs like `"@s.whatsapp.net"`.
- Adds 12 new edge case tests covering empty strings, whitespace, non-digit inputs, bare prefixes, and valid number normalization.
- The changes are backward-compatible — return type stays `string`, and callers that already handle empty strings or use `.filter(Boolean)` are unaffected.
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge — it fixes a clear bug with well-tested, minimal, backward-compatible changes.
- The changes are narrowly scoped to three functions, all with correct logic. The fix addresses a real bug where invalid inputs produced a bare "+" that downstream code treated as valid. The new behavior (returning empty string) is properly guarded in all three modified functions. 12 new tests cover the edge cases thoroughly. I examined 22+ call sites across the codebase and found no callers that would break from this change — callers already use `.filter(Boolean)`, length checks, or truthy guards. The redundant `normalizedN !== ""` check in `isSelfChatMode` is defensive but harmless.
- No files require special attention
<sub>Last reviewed commit: 9594192</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#19590: fix(whatsapp): normalize Brazilian mobile numbers in JID conversion
by qualiobra · 2026-02-18
77.7%
#16655: fix(whatsapp): resolve reply-to sender E.164 for LID JIDs (have bot...
by mascarenhas · 2026-02-15
76.6%
#15786: fix: strip device suffix from selfJid in WhatsApp mention matching
by kenken64 · 2026-02-13
76.0%
#8052: fix(whatsapp): strip leading whitespace from outbound messages
by FelixFoster · 2026-02-03
75.8%
#7395: fix(whatsapp): strip markdown bold/italic from URLs before sending
by lailoo · 2026-02-02
75.7%
#10196: fix(whatsapp): sanitize raw mention IDs in outbound messages
by koala73 · 2026-02-06
75.5%
#22106: fix(whatsapp): honor selfChatMode override for group mentions
by sportclaw · 2026-02-20
75.3%
#5665: fix: match group JIDs in groupAllowFrom allowlist
by koala73 · 2026-01-31
75.0%
#20190: WhatsApp: normalize BR 9th-digit outbound targets
by saurabhchopade · 2026-02-18
74.9%
#16733: fix(ui): avoid injected newlines when tool output is hidden
by jp117 · 2026-02-15
74.5%