vercel / vercel/chat

# [@chat-adapter/teams] Adaptive-card button clicks return an empty invoke response → Teams shows "Unable to reach app" and retries the invoke (duplicate action delivery)

Open Beginner friendly
#731 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
TypeScript
Stars
2.4k
Forks
314
Avg merge
1d 18h
Merged PRs (30d)
63

Description

Bug Description

When a user clicks a button (Action.Submit) on an adaptive card posted by a
Teams bot, the adapter's card.action invoke handler always replies with an
empty AdaptiveCardInvokeResponse (value: ""). Teams treats this as an
unsatisfactory/malformed invoke result, renders the generic "Unable to reach
app. Please try again."
banner, and retries the invoke. The retry causes
the registered onAction handler to run a second time, so any side effect the
handler performs (post a message, create a ticket, resume a workflow, etc.)
happens twice for a single click.

Steps to Reproduce
  1. Build a Teams bot with @chat-adapter/teams.
  2. Post an adaptive card containing a Button (rendered as Action.Submit):
    <Card>
      <Text>Was this helpful?</Text>
      <Actions>
        <Button id="thumbs_up" label="👍" value={JSON.stringify({ id: "x" })} />
        <Button id="thumbs_down" label="👎" value={JSON.stringify({ id: "x" })} />
      </Actions>
    </Card>
    
  3. Register a handler:
    bot.onAction(["thumbs_up", "thumbs_down"], async (event) => {
      console.log("clicked", event.actionId);
      await event.thread?.post("received");
    });
    
  4. In Teams, click the button once.
Expected Behavior
  • The button click is acknowledged; no error banner.
  • The onAction handler runs exactly once.
Actual Behavior
  • Teams shows "Unable to reach app. Please try again." on the card.
  • The onAction handler runs twice (Teams retries the invoke). Logs show
    the same actionId / activity processed two times, and any side effect is
    duplicated.
Code Sample

Chat SDK Version

4.29.0

Node.js Version

No response

Platform Adapter

Microsoft Teams

Operating System

None

Additional Context

The card.action event handler returns a fixed invoke response whose value
is an empty string:

// packages/adapter-teams/src/index.ts  (card.action handler)
this.app.on("card.action", async (ctx) => {
  this.cacheUserContext(ctx.activity);
  await this.handleAdaptiveCardAction(ctx);
  return {
    statusCode: 200,
    type: "application/vnd.microsoft.activity.message",
    value: "",   // ← empty message value
  };
});

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in packages/adapter-teams/src/index.ts at the card.action handler and its Adaptive Card invoke response. Reproduce the single-button-click flow with the issue's onAction example, then verify that Teams receives an acknowledged response, shows no error banner, and invokes the handler only once.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
82/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.