Skip to content

fix(msteams): forward messageBack card actions (Action.Submit) to agent (#60952)#64503

Open
ndholakia wants to merge 3 commits intoopenclaw:mainfrom
ndholakia:fix/msteams-messageback-card-action-60952
Open

fix(msteams): forward messageBack card actions (Action.Submit) to agent (#60952)#64503
ndholakia wants to merge 3 commits intoopenclaw:mainfrom
ndholakia:fix/msteams-messageback-card-action-60952

Conversation

@ndholakia
Copy link
Copy Markdown

Summary

Forward Action.Submit messageBack activities to the agent instead of dropping them as empty messages. Closes #60952.

When a user clicks an Action.Submit button on an Adaptive Card in Teams, the click is sent as a messageBack activity (type="message" with empty text and a value object). The message handler strips mentions, finds empty text, and drops the message with "skipping empty message after stripping mentions." The value payload is never forwarded to the agent.

This change adds a check for activity.value before the empty-text drop at line 452 of message-handler.ts. If the value payload is a non-empty object, it serializes it as [CARD_ACTION] {json} so the agent receives the button payload. This is consistent with:

The detection pattern: activity.type === "message" && !text && activity.value && typeof activity.value === "object"

Test plan

  • Send an Adaptive Card with Action.Submit buttons to a Teams DM
  • Click a button — verify the agent receives [CARD_ACTION] {"key":"value"} as the message body
  • Verify normal text messages still work (no regression)
  • Verify empty messages are still dropped (no spurious forwarding)
  • Verify Action.Execute invoke path (fix(msteams): handle Adaptive Card Action.Submit invoke activities #60431) is unaffected

Context

This is the companion fix to #55384/#60431 (Action.Execute). Together they cover both invoke formats Teams uses for Adaptive Card interactions. We've been running a sidecar HTTP proxy workaround for this since March 2026 — this fix eliminates the need for that sidecar for card actions.

🤖 Generated with Claude Code

@openclaw-barnacle openclaw-barnacle Bot added channel: msteams Channel integration: msteams size: XS labels Apr 10, 2026
@ClawKingAI

This comment was marked as spam.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 10, 2026

Greptile Summary

This PR fixes a long-standing issue where Action.Submit clicks on Adaptive Cards in Teams were silently dropped because the Bot Framework sends them as type=\"message\" activities with empty text and the payload in activity.value. The fix inserts a check after the initial rawBody computation (lines 197–215): if rawBody is empty but activity.value is a non-empty object, rawBody is set to [CARD_ACTION] {json} before the empty-body guard fires. The implementation is consistent with the existing adaptiveCard/action invoke path and does not affect poll votes (which always have non-empty text).

Confidence Score: 4/5

Safe to merge; the logic is correct and isolated, but the new code path has no automated test.

The fix is functionally correct and well-scoped. The only open item is the absence of an automated test for the new messageBack path — the analogous invoke path already has coverage in monitor-handler.adaptive-card.test.ts and a parallel test here would prevent silent regressions. All other findings are P2.

extensions/msteams/src/monitor-handler/message-handler.ts — new card-action branch lacks automated test coverage.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/msteams/src/monitor-handler/message-handler.ts
Line: 197-215

Comment:
**Missing automated test for the new messageBack path**

The analogous `adaptiveCard/action` invoke path (PR #60431) has coverage in `monitor-handler.adaptive-card.test.ts`, but no test was added for this new `messageBack`/`Action.Submit` path. Without a test, a future refactor of the empty-body drop guard could silently re-introduce the regression.

A minimal case to add to `monitor-handler.adaptive-card.test.ts` would send a `type: "message"` activity with empty text and a non-empty `value` object (e.g. `{ intent: "approve", env: "prod" }`) and assert that `dispatchReplyFromConfigWithSettledDispatcher` is called once with `RawBody` matching `[CARD_ACTION] {"intent":"approve","env":"prod"}`.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(msteams): forward messageBack card a..." | Re-trigger Greptile

Comment on lines +197 to +215
// If text and attachments are both empty but activity.value is present,
// this is a messageBack card action (Action.Submit). Serialize the value
// payload so it reaches the agent instead of being dropped. (#60952)
let rawBody = text || attachmentPlaceholder;
if (
!rawBody &&
activity.value != null &&
typeof activity.value === "object" &&
Object.keys(activity.value as Record<string, unknown>).length > 0
) {
try {
rawBody = `[CARD_ACTION] ${JSON.stringify(activity.value)}`;
log.info("messageBack card action detected; forwarding value payload", {
valueKeys: Object.keys(activity.value as Record<string, unknown>),
});
} catch {
log.warn("failed to serialize messageBack value payload");
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing automated test for the new messageBack path

The analogous adaptiveCard/action invoke path (PR #60431) has coverage in monitor-handler.adaptive-card.test.ts, but no test was added for this new messageBack/Action.Submit path. Without a test, a future refactor of the empty-body drop guard could silently re-introduce the regression.

A minimal case to add to monitor-handler.adaptive-card.test.ts would send a type: "message" activity with empty text and a non-empty value object (e.g. { intent: "approve", env: "prod" }) and assert that dispatchReplyFromConfigWithSettledDispatcher is called once with RawBody matching [CARD_ACTION] {"intent":"approve","env":"prod"}.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/msteams/src/monitor-handler/message-handler.ts
Line: 197-215

Comment:
**Missing automated test for the new messageBack path**

The analogous `adaptiveCard/action` invoke path (PR #60431) has coverage in `monitor-handler.adaptive-card.test.ts`, but no test was added for this new `messageBack`/`Action.Submit` path. Without a test, a future refactor of the empty-body drop guard could silently re-introduce the regression.

A minimal case to add to `monitor-handler.adaptive-card.test.ts` would send a `type: "message"` activity with empty text and a non-empty `value` object (e.g. `{ intent: "approve", env: "prod" }`) and assert that `dispatchReplyFromConfigWithSettledDispatcher` is called once with `RawBody` matching `[CARD_ACTION] {"intent":"approve","env":"prod"}`.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7edd1d1047

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +203 to +205
activity.value != null &&
typeof activity.value === "object" &&
Object.keys(activity.value as Record<string, unknown>).length > 0
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept non-object messageBack payloads

This new branch only forwards card actions when activity.value is an object with at least one key, so messageBack actions that carry a string/primitive payload (or {}) still hit the later empty-body guard and get dropped. In Teams, Action.Submit/messageBack can be configured without object-shaped data, so those button clicks remain invisible to the agent despite this fix. Consider serializing activity.value with the same rules used in the invoke path (string + JSON fallback) instead of requiring a non-empty object.

Useful? React with 👍 / 👎.

@ndholakia ndholakia force-pushed the fix/msteams-messageback-card-action-60952 branch from 7edd1d1 to 7964480 Compare April 10, 2026 21:45
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 79644805c4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

const deps = createDeps();
const { handler, run } = createActivityHandler();
const registered = registerMSTeamsHandlers(handler, deps) as MSTeamsActivityHandler & {
const _registered = registerMSTeamsHandlers(handler, deps) as MSTeamsActivityHandler & {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore registered handle in invoke test setup

Renaming the handler registration variable to _registered breaks this test because it still calls registered.run(...) later, which is now an undefined identifier at runtime. As written, the adaptiveCard/action invoke test throws before assertions, so this regression path is no longer validated and the test file can fail immediately when that case runs.

Useful? React with 👍 / 👎.

@ndholakia
Copy link
Copy Markdown
Author

Hi team — friendly bump on this PR. All 3 test cases requested by Greptile review have been added (commit 7964480). CI is green on all msteams-specific jobs. This fixes a real Teams bot interop issue where Action.Submit button clicks are silently dropped. Happy to address any additional feedback. Thanks!

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 27, 2026

Codex review: needs real behavior proof before merge.

Summary
The PR updates the Microsoft Teams inbound handler to serialize empty-text messageBack activity.value payloads as [CARD_ACTION] ..., adds regression tests, and adds a changelog entry.

Reproducibility: yes. A high-confidence source reproduction is a Teams type: "message" activity with empty text, no attachment placeholder, and a non-null value; current main leaves rawBody empty and returns at the empty-message guard.

Real behavior proof
Needs real behavior proof before merge: No screenshot, recording, terminal output, live output, linked artifact, or redacted runtime log shows an after-fix Teams button click reaching the agent; tests and CI are supplemental only. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, ask a maintainer to comment @clawsweeper re-review.

Next step before merge
Human follow-up is needed to collect and verify real Teams behavior proof; there is no narrow code repair left after the prepared head.

Security
Cleared: The diff only changes Microsoft Teams inbound serialization, tests, and changelog text; it adds no dependencies, workflows, secret handling, downloads, or new code-execution paths.

Review details

Best possible solution:

Keep this PR open and land the prepared head only after redacted real Teams Action.Submit/messageBack proof is posted and required checks finish green.

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

Yes. A high-confidence source reproduction is a Teams type: "message" activity with empty text, no attachment placeholder, and a non-null value; current main leaves rawBody empty and returns at the empty-message guard.

Is this the best way to solve the issue?

Yes for the code direction. The handler-level serialization before the empty-message guard is narrow and regression-tested, but merge should wait for real Teams proof of the prepared head.

Acceptance criteria:

  • Ask for a redacted screenshot, recording, terminal output, live output, linked artifact, or runtime log showing a real Teams Action.Submit/messageBack button click after the fix and the resulting agent-visible [CARD_ACTION] ... body.
  • Confirm the Real behavior proof check passes on head 55a5ad889a4118b9a788ec1c5a613bfc1b89ab98.
  • Before merge, confirm required CI/checks are green for the exact head SHA.

What I checked:

  • Current main drops the reported activity shape: At current main, rawBody is built only from stripped text or attachment placeholders, and the empty-body guard returns before dispatch when Teams sends empty text with a value payload. (extensions/msteams/src/monitor-handler/message-handler.ts:207, df22284f8550)
  • PR head forwards messageBack values: The prepared head changes rawBody to mutable text and fills it from activity.value when the normal message body is empty, using string passthrough or JSON serialization with an optional warning logger. (extensions/msteams/src/monitor-handler/message-handler.ts:207, 55a5ad889a41)
  • Regression tests cover the new path: The PR head adds tests for object-valued messageBack payloads, string-valued payloads, and genuinely empty messages that should still be dropped. (extensions/msteams/src/monitor-handler.adaptive-card.test.ts:164, 55a5ad889a41)
  • Changelog blocker is resolved: The prepared head includes a single-line user-facing Fixes entry crediting the contributor for the Teams messageBack card-action behavior. (CHANGELOG.md:190, 55a5ad889a41)
  • Dependency contract supports the reported activity shape: Microsoft Teams documentation says Action.Submit object data populates the activity value property while text is empty, and messageBack value may be a JSON string or object.
  • CI/proof state: Head 55a5ad889a4118 has broad successful checks, but the dedicated Real behavior proof check completed with failure and one Critical Quality check was still in progress when inspected. (55a5ad889a41)

Likely related people:

  • BradGroux: Authored the merged Microsoft Teams adaptive-card invoke handling that this messageBack path complements, and prepared the latest head for this PR. (role: adjacent feature owner and recent maintainer; confidence: high; commits: 06c6ff6670cd, 55a5ad889a41; files: extensions/msteams/src/monitor-handler.ts, extensions/msteams/src/monitor-handler.adaptive-card.test.ts, extensions/msteams/src/monitor-handler/message-handler.ts)
  • sudie-codes: Recently merged nearby Teams invoke, message-action, and message-handler work, including fileConsent/invoke and thread-context behavior that share this monitor surface. (role: recent Microsoft Teams monitor maintainer; confidence: medium; commits: 784318799bf0, 01ea7e49212a, 0f192710924f; files: extensions/msteams/src/monitor-handler.ts, extensions/msteams/src/monitor-handler/message-handler.ts)
  • steipete: Recent shared channel turn-kernel and Teams hot-test refactors touched the same handler/test surface and affect dispatch semantics around inbound message bodies. (role: recent channel runtime refactor maintainer; confidence: medium; commits: 9a9cd0c0abdf, 02ebac6250bf, ffe67e9cdc9e; files: extensions/msteams/src/monitor-handler/message-handler.ts, extensions/msteams/src/monitor-handler.adaptive-card.test.ts)

Remaining risk / open question:

  • After-fix real Teams behavior proof is absent; tests and CI do not prove that an actual tenant delivers the expected [CARD_ACTION] ... body after a button click.
  • The exact head still had a failing Real behavior proof check and incomplete overall status when inspected.

Codex review notes: model gpt-5.5, reasoning high; reviewed against df22284f8550.

ndholakia and others added 3 commits May 8, 2026 12:11
…nt (openclaw#60952)

When a user clicks an Action.Submit button on an Adaptive Card in Teams,
the click arrives as a messageBack activity (type="message" with empty
text and a value object). The message handler strips mentions, finds
empty text, and drops the message as "skipping empty message."

This change checks for activity.value before the empty-text drop. If
the value payload is a non-empty object, it serializes it as
`[CARD_ACTION] {json}` so the agent receives the button payload.

The detection pattern matches the existing invoke-proxy sidecar
workaround and is consistent with the adaptiveCard/action invoke
handling added in openclaw#60431.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…essageBack tests

The original adaptiveCard/action invoke test uses registered.run() which
requires the variable name. The three new messageBack tests call the mock
run() directly and only need registerMSTeamsHandlers for its side effect.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@BradGroux BradGroux force-pushed the fix/msteams-messageback-card-action-60952 branch from f3bb08c to 55a5ad8 Compare May 8, 2026 17:13
@BradGroux
Copy link
Copy Markdown
Member

Maintainer update: rebased this onto current main and prepared the branch at 55a5ad889a4118b9a788ec1c5a613bfc1b89ab98.

What changed in the prepared head:

  • Guarded the optional warning logger in the messageBack serialization fallback.
  • Added the required changelog entry with contributor attribution.
  • Kept the existing messageBack forwarding tests and empty-message drop behavior intact.

Verification run locally:

  • pnpm test -- extensions/msteams/src/monitor-handler.adaptive-card.test.ts (4 tests passed)
  • pnpm build
  • pnpm check

Waiting on fresh CI/ClawSweeper for this prepared head before merge.

@openclaw-barnacle openclaw-barnacle Bot added the triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup. label May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: msteams Channel integration: msteams size: S triage: needs-real-behavior-proof Candidate: external PR needs after-fix proof from a real setup.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: MS Teams plugin should forward messageBack card actions (Action.Submit) to the agent

3 participants