fix: extract sender.id from object-shaped sender field (real #46) - #56
Conversation
OpenClaw's Conversation info metadata block emits sender as an object:
"sender": { "id": "378376356108435457", "name": "bigrob8181", ... }
The previous string-only fallback (parsed.sender_id ?? parsed.sender) returned
undefined when parsed.sender was an object, causing every Discord/HA-Assist
message to fall through to the OWNER_ID default peer instead of the actual
sender. This is the root cause of the "everything goes to owner instead of rob"
attribution bug.
Extraction priority is now:
1. parsed.sender_id (top-level string) — canonical envelope contract
2. parsed.sender (string) — legacy shape, preserved for back-compat
3. parsed.sender.id (object.id string) — current OpenClaw/HA-Assist shape
Tests cover Discord envelope shape, HA Assist envelope shape, priority
ordering, and rejection of non-string / empty sender.id values.
Distinct from upstream plastic-labs#77 (fork PR #46), which removed the string fallback
entirely and did not add object-shape support. That PR is DO NOT MERGE per
the Codex review consensus.
Codex review flagged that `parsed.sender_id ?? parsed.sender` uses value-presence fallback rather than type-aware fallback. A malformed higher-priority field (e.g. `sender_id: 12345`) would short-circuit the operator and skip the valid `sender.id` object fallback, returning undefined instead. Each of the three extraction paths is now type-guarded independently: 1. sender_id (must be non-empty string) 2. sender (must be non-empty string) 3. sender.id (must be non-empty string) A malformed candidate at any level falls through to the next path. Two additional tests: - non-string sender_id falls back to valid sender.id object - null sender_id falls back to legacy sender string
Codex Review (gpt-5.5)Automated adversarial review via codex exec. Verdict: MERGE WITH CHANGESThis is the right minimal fix for the production bug. It correctly handles the OpenClaw-emitted shape: sender: { id, name, username }and it preserves the legacy Findings1. Comment says “sender_id string → sender.id”, but code does Current code: const candidate = parsed.sender_id ?? parsed.sender;If Example: {
"sender_id": 123,
"sender": { "id": "rob" }
}Expected from the new comment: return This is probably not relevant to the known OpenClaw bug, but the comment promises type-aware fallback and the implementation does value-presence fallback. Suggested fix: if (typeof parsed.sender_id === "string" && parsed.sender_id.length > 0) {
return parsed.sender_id;
}
const sender = parsed.sender;
if (typeof sender === "string" && sender.length > 0) {
return sender;
}
if (sender && typeof sender === "object") {
const objId = (sender as { id?: unknown }).id;
if (typeof objId === "string" && objId.length > 0) {
return objId;
}
}Add one test for malformed 2. Test count mismatch The prompt says 7 new tests, but the shown diff adds 6 tests. Not a code issue, but worth reconciling before merge. 3. Security posture is unchanged, not improved This continues to trust sender metadata embedded in message content. Adding object support does not materially worsen spoofing risk because the old string paths already allowed trusted metadata to specify sender identity. That said, if arbitrary users can submit raw messages containing the metadata block, then both old and new behavior are spoofable. This PR should not be considered a security boundary. Sender attribution needs to remain enforced at envelope creation / ingestion boundary, not by this parser. CoverageThe new tests cover the important path:
Missing edge case:
SimplicityThe change is small and scoped. No dead code, no over-engineering. The only issue is the Final RecommendationMerge after changing the fallback logic to be type-aware and adding the one malformed-
|
Codex Re-Review (gpt-5.5) — after fixup 81ca94eFinding fully addressed. The fixup removes the value-presence fallback bug: No new fixup-introduced issues found. Remaining edge cases worth noting, but not merge blockers:
Verdict: MERGE. |
What
Fixes the actual sender-attribution bug that our Discord and HA Assist channels hit today. The Conversation info metadata block emits
senderas a structured object ({id, name, username}), butextractSenderIdonly handled the string shape, so every message fell through toOWNER_IDand landed on the wrong peer.Why not upstream plastic-labs#77 / fork PR #46
Per the three Codex reviews on #46: that PR removed the legacy string fallback (behavioral regression), didn't parse the object shape OpenClaw actually emits, had a build-breaking
toolCtxundefined reference across multiple tools, and defaulted new deploys to aper-senderpolicy that would create a third peer split under raw platform IDs.This PR does the minimum: adds object-shape extraction, keeps every existing path working, no new config knobs, no policy changes.
Extraction priority (in order)
parsed.sender_id— top-level string, canonical envelope contractparsed.sender— string, legacy shape preserved for back-compatparsed.sender.id— object.id string, current OpenClaw + HA Assist shapeTests
7 new unit tests in
test/helpers.test.ts:sender: {id, name, username})sender: {id, name, source: "ha_assist"})sender_idstring wins oversender.idobject.id.idthat is not a string.idvitest run test/helpers.test.ts→ 27 passed.Downstream
Once merged and rebuilt into the OCI image (
ghcr.io/clawd-ops/openclaw-honcho:latest), the home-ops PR that wires the fork into OpenClaw will pick this up automatically. No plugin config changes required on the deployment side.