Skip to content

fix(signal): route username targets as usernames, not phone numbers#96308

Closed
ly-wang19 wants to merge 2 commits into
openclaw:mainfrom
ly-wang19:fix/signal-username-target-route
Closed

fix(signal): route username targets as usernames, not phone numbers#96308
ly-wang19 wants to merge 2 commits into
openclaw:mainfrom
ly-wang19:fix/signal-username-target-route

Conversation

@ly-wang19

Copy link
Copy Markdown
Contributor

Summary

resolveSignalOutboundTarget (extensions/signal/src/outbound-session.ts) handled the documented username:/u: Signal send grammar by stripping the prefix to the bare name — and then fell through to the phone/uuid resolver. resolveSignalSender treats any non-empty sourceNumber as a phone and runs it through normalizeE164, which strips all non-digits. So:

  • username:alice.42 → phone +42
  • username:kevin+ (empty)
  • signal:username:bob.99+99

Both the returned to/from and the RoutePeer id (the session key) are corrupted, so an agent replying/sending to a Signal username delivers to a wrong/empty phone number (or fails) and is keyed to the wrong session. Group and phone/uuid targets resolve correctly; only the username grammar was mis-dispatched.

Usernames are a first-class, documented send target (docs/channels/signal.md), and the sibling send.ts parseTarget maps username:/u: to a distinct username send — resolveSignalOutboundTarget was the one target-grammar surface that didn't honor it.

Changes

  • outbound-session.ts — add a dedicated username branch (after the group: branch) that returns { peer: { kind: "direct", id: "username:<name>" }, chatType: "direct", to/from: "signal:username:<name>" }, and remove the prefix-stripping that fell through into the phone resolver.
  • outbound-session.test.ts — new colocated test (username + group + phone).

Real behavior proof

Behavior addressed: a Signal username:/u: target was digit-stripped into a bogus phone number, corrupting delivery and the session key. After the fix the username grammar is preserved.

Real environment tested: Node 22.22.1, macOS, no network/model. Vitest exercises the real exported resolveSignalOutboundTarget. BEFORE is origin/main's file (swapped via git show).

Exact steps or command run after this patch:

node scripts/run-vitest.mjs extensions/signal/src/outbound-session.test.ts

Evidence after fix:

resolveSignalOutboundTarget("username:alice.42").to
  BEFORE: "signal:+42"                      AFTER: "signal:username:alice.42"
resolveSignalOutboundTarget("u:alice.42").to / .peer.id
  BEFORE: "signal:+42" / "+42"              AFTER: "signal:username:alice.42" / "username:alice.42"
resolveSignalOutboundTarget("group:abc==")  BOTH: group target, unchanged
resolveSignalOutboundTarget("+15551234567") BOTH: "signal:+15551234567", unchanged

Observed result after fix: username targets keep their grammar (correct delivery target + session key); group/phone/uuid paths are unchanged. The new test fails on origin/main and passes after.

What was not tested: a live Signal send (the test drives the real resolveSignalOutboundTarget, which the outbound route + agent-delivery paths consume to set the delivery target).

Additional checks

  • node scripts/run-vitest.mjs extensions/signal/src/outbound-session.test.ts passes; fail-before verified by swapping in origin/main's file. Extension oxlint clean.

@openclaw-barnacle openclaw-barnacle Bot added channel: signal Channel integration: signal size: S labels Jun 24, 2026
@clawsweeper

clawsweeper Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 1, 2026, 3:32 AM ET / 07:32 UTC.

Summary
The PR adds a Signal outbound-session username branch and colocated tests so username:/u: targets resolve to username route metadata instead of the phone resolver.

PR surface: Source +12, Tests +31. Total +43 across 2 files.

Reproducibility: yes. Source inspection shows current main strips username:/u: and passes the remaining value as a phone-like sourceNumber; the PR body also supplies before/after output from the exported resolver.

Review metrics: 1 noteworthy metric.

  • Username session-key mapping: 1 target family changed. Signal username targets move from phone-normalized peer ids to canonical username peer ids, which fixes routing but needs an explicit session-continuity decision.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🐚 platinum hermit
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P2] Have a maintainer explicitly accept the no-migration tradeoff for pre-fix username session keys before merge.

Risk before merge

  • [P1] Merging intentionally moves username outbound sessions from digit-stripped phone-like peer ids to canonical username:<name> ids, so any pre-fix corrupted session continuity is not migrated.

Maintainer options:

  1. Accept canonical username session keys (recommended)
    Merge with no migration, accepting that prior username-targeted sessions keyed under digit-stripped phone-like ids were already corrupt and ambiguous.
  2. Add an upgrade caveat
    Before merge, add a short PR-body note that username-targeted outbound sessions move to canonical username:<name> keys and old corrupted keys are not migrated.
  3. Pause for migration design
    Pause only if maintainers require preserving old username-targeted history, because the old digit-stripped keys can collide and are not safely attributable to the original username.

Next step before merge

  • [P2] Maintainer review should accept or reject the no-migration tradeoff for corrupted pre-fix username session keys; there is no narrow automation repair identified.

Security
Cleared: The diff only changes Signal TypeScript routing logic and adds a colocated test, with no dependency, workflow, secret, install, or supply-chain surface change.

Review details

Best possible solution:

Land the plugin-owned route fix after maintainers accept that ambiguous pre-fix username sessions should not be migrated.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection shows current main strips username:/u: and passes the remaining value as a phone-like sourceNumber; the PR body also supplies before/after output from the exported resolver.

Is this the best way to solve the issue?

Yes. The Signal plugin outbound-session resolver is the narrowest maintainable fix because core delegates route semantics to channel plugins and the existing Signal send path already has a username target kind.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against b63e06f68aa0.

Label changes

Label justifications:

  • P2: This is a narrow Signal routing bug that can corrupt username delivery and session metadata, but it is limited to one channel target grammar.
  • merge-risk: 🚨 session-state: The PR intentionally changes username target session keys from digit-stripped phone-like ids to canonical username ids without migrating any pre-fix corrupted keys.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🐚 platinum hermit and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR body includes before/after output from the real exported resolver plus a focused Vitest command for the changed route behavior; no contributor action is needed for proof.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes before/after output from the real exported resolver plus a focused Vitest command for the changed route behavior; no contributor action is needed for proof.
Evidence reviewed

PR surface:

Source +12, Tests +31. Total +43 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 1 17 5 +12
Tests 1 31 0 +31
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 2 48 5 +43

What I checked:

Likely related people:

  • steipete: Commit history shows 6f6468027a21 extracted the Signal outbound target resolver with the username fallthrough, and ddde144cb65a later touched the same resolver/helper normalization path. (role: introduced behavior and recent area contributor; confidence: high; commits: 6f6468027a21, ddde144cb65a; files: extensions/signal/src/outbound-session.ts, extensions/signal/src/channel.ts)
  • scoootscooob: Commit 4540c6b3bc12 moved the Signal channel implementation into extensions/signal/src, preserving the owner boundary for the affected plugin files. (role: adjacent migration contributor; confidence: medium; commits: 4540c6b3bc12; files: extensions/signal/src/channel.ts, extensions/signal/src/send.ts)
  • joshavant: Commit 646817dd808b touched Signal outbound send/channel paths while unifying resolved config threading across send paths near the affected route surface. (role: recent adjacent contributor; confidence: medium; commits: 646817dd808b; files: extensions/signal/src/send.ts, extensions/signal/src/channel.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. labels Jun 24, 2026
resolveSignalOutboundTarget stripped the username:/u: prefix to the bare name
and then fed it to the phone/uuid resolver, where normalizeE164 digit-strips the
value — so "username:alice.42" resolved to phone "+42", "username:kevin" to "+",
corrupting both the delivery target and the session key. Add a dedicated
username branch (twin of send.ts parseTarget) that keeps the username grammar
intact; group/phone/uuid targets are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@ly-wang19
ly-wang19 force-pushed the fix/signal-username-target-route branch from 0524e48 to 8fdf1e7 Compare June 28, 2026 05:16
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. label Jun 28, 2026
@steipete steipete self-assigned this Jul 14, 2026
@steipete

Copy link
Copy Markdown
Contributor

Superseded by #107365, now landed as 4ea1c71.

The landed fix keeps username targets out of phone normalization for both outbound session routing and direct sends, while preserving phone, UUID, and group behavior. It also carries @ly-wang19 as co-author. Thank you for finding and driving this bug.

@steipete steipete closed this Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: signal Channel integration: signal merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants