Skip to content

## Summary #3422

Description

@nexoracontrol-ops

Summary

Running doctor on a config that still uses the legacy channels.whatsapp.ackReaction object silently drops DM acknowledgement reactions whenever the legacy settings mean "ack DMs plus mentioned groups" (direct: true with group: "mentions"). That combination is also the legacy default, so a plain ackReaction: { "emoji": "..." } entry is affected too. After migration the config ends up with messages.ackReactionScope: "group-mentions", which the WhatsApp runtime interprets as DMs disabled, and the legacy object is deleted so the original intent is unrecoverable. Doctor reports the migration as a clean move.

Environment

  • Commit: 62c148d7d003dc67e4b1d6fea71737b9dca61e14 (origin/main at time of filing)
  • Surface: doctor legacy config migration for WhatsApp ack reactions, plus the WhatsApp auto-reply ack reaction monitor

Steps to reproduce

  1. Create an isolated state dir with a legacy WhatsApp ack config:
    {
      "channels": {
        "whatsapp": {
          "ackReaction": { "direct": true, "group": "mentions", "emoji": "👀" }
        }
      }
    }
  2. Run doctor against it: OPENCLAW_STATE_DIR=<dir> node scripts/run-node.mjs doctor --non-interactive --fix
  3. Inspect the rewritten openclaw.json: the legacy object is gone and messages.ackReactionScope is "group-mentions".
  4. Deliver a DM through the WhatsApp auto-reply monitor (maybeSendAckReaction) with the migrated config: no ack reaction is sent. A group message with a mention still gets one.

Expected

A legacy config that acked DMs keeps acking DMs after migration. If the migration cannot represent the legacy combination, it should say so (doctor note or warning) instead of deleting the legacy object and reporting a clean move.

Actual

The migration writes no explicit scope for direct: true plus group: "mentions", deletes channels.whatsapp.ackReaction, and logs "Moved translatable channels.whatsapp.ackReaction settings to messages ack settings." Default materialization then persists ackReactionScope: "group-mentions". The runtime treats that scope as directEnabled = false, so DM acknowledgements stop with no signal to the user.

Root cause (if known)

src/commands/doctor/shared/legacy-config-migrations.runtime.retired.ts:356-375. The mapping ladder covers five of the six legacy combinations and yields undefined for direct && group === "mentions":

const direct = ack.direct !== false;
const group = ack.group ?? "mentions";
const scope =
  direct && group === "always"
    ? "all"
    : direct && group === "never"
      ? "direct"
      : !direct && group === "always"
        ? "group-all"
        : !direct && group === "mentions"
          ? "group-mentions"
          : !direct && group === "never"
            ? "off"
            : undefined;
if (scope) {
  messages.ackReactionScope = scope;
}
// ...
delete entry.ackReaction;

The unmapped combination is exactly the legacy default (direct defaults to true, group defaults to "mentions"). The shared ackReactionScope enum has no value meaning "DMs plus mentioned groups": all widens groups to every message, direct drops groups, group-mentions drops DMs (extensions/whatsapp/src/auto-reply/monitor/ack-reaction.ts:43-55 derives directEnabled = scope === "all" || scope === "direct"). So a lossless 1:1 mapping is impossible with the current enum; the bug is that the migration silently picks the lossy default and destroys the legacy source.

Sibling surfaces

The messages.ackReactionScope interpretation is shared with other channels, but the documented DM gotcha there (#84233) applies to users who chose a scope explicitly. Only WhatsApp had the legacy per-channel {direct, group} object, so the silent-migration loss is WhatsApp-specific. #91347 (whether doctor should create channels.whatsapp at all during this migration) and #109979 (LID sender identities in group acks) do not touch the scope mapping.

Real behavior proof

Behavior addressed: doctor migration of legacy channels.whatsapp.ackReaction with direct: true, group: "mentions" loses DM acknowledgement reactions
Real environment tested: real doctor CLI (node scripts/run-node.mjs doctor --non-interactive --fix) against an isolated state dir, then the real WhatsApp auto-reply monitor (maybeSendAckReaction with the real config loader and real admission builder), at commit 62c148d7d003dc67e4b1d6fea71737b9dca61e14
Exact steps or command run after this patch: OPENCLAW_STATE_DIR=<dir> node scripts/run-node.mjs doctor --non-interactive --fix on the legacy config above, then drove maybeSendAckReaction with the migrated config for a DM and for a group message with a mention
Evidence after fix:

# OBSERVED (buggy, origin/main 62c148d7d003)
# doctor change note:
Moved translatable channels.whatsapp.ackReaction settings to messages

# migrated openclaw.json (legacy channels.whatsapp.ackReaction deleted):
"messages": {
  "ackReaction": "👀",
  "ackReactionScope": "group-mentions"
}

# real WhatsApp monitor decisions with the migrated config:
DM from paired user: ack SKIPPED (no reaction)
Group message with @mention: ack SENT (emoji 👀)

# same DM when the scope permits directs (shows only the migrated scope suppresses it):
DM from paired user: ack SENT (emoji 👀)

# EXPECTED (correct behavior on identical inputs)
The migrated config preserves DM acknowledgements, or doctor surfaces a warning that
the legacy direct-plus-group-mentions combination cannot be represented, instead of
deleting the legacy object and reporting a clean move.

Observed result after fix: the migration discards the DM half of the legacy ack scope, the rewritten config suppresses DM ack reactions, and the same DM is acked again the moment the scope permits directs
What was not tested: delivery of the reaction over a live WhatsApp socket (no linked account on this machine; the send was attempted and only failed at the socket layer), other channels' runtime interpretation of the defaulted scope, and the interactive doctor flow (the non-interactive flag was used)

Additional notes

The emoji-only legacy form "ackReaction": { "emoji": "👀" } (legacy defaults for both direct and group) migrates to the same "group-mentions" scope and loses DM acks identically, verified with a second doctor run on a fresh state dir.

Originally posted by @yetval in openclaw/openclaw#112796

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions