Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

requestApproval: a click from a non-approver locks the real approver out of Approve

Open
#1,075 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
node.js, typescript
Domain
api, backend

Research direction

Start by reading packages/chat/src/callback-url.ts around resolveCallbackUrl and packages/chat/src/workflow/approval.test.ts around the existing unauthorized-click test. Reproduce the sequence in the issue: a non-approver clicks Approve, then the approver clicks the same button. Done means the approver's click reaches the workflow and approves, while unauthorized users still cannot approve.

Written by the indexing model from the issue text.

Description

Bug description

If someone outside approvers clicks Approve on a requestApproval card, the real approver can't approve anymore. Their Approve click does nothing. They can still click Deny, or wait for the timeout.

Non-approvers still can't approve, but one stray click from them is enough to block the approval.

Steps to reproduce
  1. Call requestApproval(thread, { title: "Deploy?", approvers: ["U_ALICE"] }).
  2. U_MALLORY clicks Approve. She gets the "isn't authorized" notice, as expected.
  3. U_ALICE clicks Approve.
Expected behavior

Step 3 approves.

Actual behavior

Alice's click never reaches the workflow webhook, so the workflow keeps waiting. Deny still works because it has its own token.

Cause

The card's Approve and Deny buttons are callbackUrl buttons (approval-card.ts#L63-L69). Each button's token is single use: resolveCallbackUrl deletes it on the first click from anyone (callback-url.ts#L177). The approvers check only runs later, inside requestApproval, after that click has already been POSTed to the webhook (workflow/index.ts#L174). By the time Alice clicks, the Approve token is gone, resolveCallbackUrl returns null, and nothing is forwarded (chat.ts#L1930).

The existing test "ignores clicks from users outside approvers and posts a notice" (approval.test.ts#L234) misses this for two reasons. It emits clicks straight to the webhook, so resolveCallbackUrl never runs, and the approver clicks Deny rather than the button Mallory used.

One possible fix: keep approval tokens alive until finalizeApprovalCard runs, or check the approver list before a token is consumed.

Code sample

I reproduced it with the real Slack adapter against a local fake of the Slack API, sending signed block_actions payloads. To isolate the token step, I used two plain callbackUrl buttons, the same buttons requestApproval builds, instead of running inside a workflow runtime:

// chat 4.43.0, @chat-adapter/slack 4.43.0, @chat-adapter/state-memory 4.43.0
const bot = new Chat({
  userName: "bot",
  adapters: { slack: createSlackAdapter({ botToken, signingSecret, apiUrl: fakeSlackUrl }) },
  state: createMemoryState(),
});

bot.onNewMention(async (thread) => {
  await thread.post(
    Card({
      title: "Deploy?",
      children: [
        Actions([
          Button({ id: "approve", label: "Approve", callbackUrl }),
          Button({ id: "deny", label: "Deny", callbackUrl }),
        ]),
      ],
    })
  );
});

// Signed block_actions webhooks for the posted card, one after another:
await click("U_MALLORY", approveButton); // POSTed to callbackUrl (1 POST)
await click("U_ALICE", approveButton);   // not POSTed (still 1)
await click("U_ALICE", denyButton);      // POSTed (2)
Environment

Chat SDK 4.43.0 (main at 7c351d2 has the same code), Node.js 24.21.0, Slack adapter, Linux.

Dominant language
TypeScript
Stars
2.4k
Forks
329
Avg merge
2d 2h
Merged PRs (30d)
62

Getting set up

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.

More from vercel/chat

All issues in vercel/chat

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.