Skip to content

feat(review-desk): Show Review items as Slack cards - #2468

Merged
dcramer merged 10 commits into
mainfrom
feat/review-items-in-slack
Oct 31, 2026
Merged

dcramer merged 10 commits into
mainfrom
feat/review-items-in-slack

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Part 5 of the review desk stack for #2401. Stacked on #2466.

  • Card in linked Slack threads: when an agent adds an open item, each linked Slack thread gets a card. The controls reuse the feat(chat): Let Junior ask for user input with requestUserInput #2149 requestUserInput rendering: one button per option for a single choice field, otherwise an Answer button that opens the same form. A notice shows Done. Every card shows Dismiss and links to the item on the review desk.
  • Updates in place: junior_review_item_slack_cards stores the message of each card. After every agent update or close and every person action (Slack, review desk, or Conversation card), each card changes in place. A closed card has no controls and shows who closed it and the answer.
  • Clicks act as the person: a click needs the same workspace and allowed-domain checks as a Slack mention, plus a Junior User linked to the Slack account. It calls actOnReviewItem as that User, so the first action still closes the item everywhere. The thread of the card gets the action as Slack input from the person, like an answer to requestUserInput. The other linked Conversations get the usual messages (exceptConversationId keeps the thread from getting a second copy). A click on a closed or forbidden item gets an ephemeral note and refreshes the card.
  • Privacy: a private item shows a card only in the thread that added it, because other linked threads can have people who cannot see its source. A card in a thread that can no longer show the item loses its content.
  • Best effort: a card failure is logged (review_item.slack_card.sync.failed) and never fails the item change.

Small refactors: buildSlackUserInputForm takes the callback id and metadata, so both forms share one builder. slackUserInputAnswerNote takes who text instead of a Slack user id, because a Review item closer is a Junior User. The Slack input that a click makes moved to chat/ingress/slack-interaction-input.ts so both handlers build it the same way.

Tests: tests/integration/slack/review-item-card-webhook.test.ts covers the click flow (cards in two threads, first click closes both, a stale click does nothing) and a private item (card only in the adding thread, only an assignee can act).

via David Cramer.

--

View Session [Sentry]

@vercel

vercel Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
junior-docs Ready Ready Preview Oct 31, 2026 3:27am UTC

Request Review

@blacksmith-sh

This comment has been minimized.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a214bd1. Configure here.

Base automatically changed from feat/review-items-in-conversations to main October 31, 2026 03:06
sentry-junior Bot and others added 8 commits October 31, 2026 03:06
Post a card for each open Review item in its linked Slack threads and change
it in place after each agent or person change. Card controls reuse the
requestUserInput rendering: choice buttons, an Answer form, Done for a
notice, and Dismiss. A click acts as the linked Junior User and reaches the
thread as Slack input from that person.

Co-Authored-By: David Cramer <david@sentry.io>
…hread

Co-Authored-By: David Cramer <david@sentry.io>
…r click

A card claim now stores when it started. A later sync takes over a claim
that is older than one minute, so a crash or failed write no longer
strands a thread without a card. The ts write only succeeds for the sync
that still owns the claim.

Slack card clicks use firstCloseOnly, so only the click whose conditional
close wins adds input to the thread. Two fast clicks add one Turn.

Co-Authored-By: David Cramer <david@sentry.io>
…aims

A sync now posts the card, then inserts its row. When two syncs post in
the same thread, the first row wins and the other sync deletes its card.
A card whose row cannot be saved is deleted too. This removes the
posting marker, the takeover timeout, and the rows that could stay
stuck when a sync stopped.

Co-Authored-By: David Cramer <david@sentry.io>
Card syncs of one item now run under withLock from chat/state/locks.ts.
Two syncs cannot post two cards in one thread, and each sync reads the
latest item, so the post-then-delete and re-read paths go away.
syncReviewItemSlackCards takes an item id because it reads the item
under the lock.

Co-Authored-By: David Cramer <david@sentry.io>
Remove the copy of withLock and fenceLock from api/acp/state.ts. ACP now
imports them from chat/state/locks.ts. Every ACP caller already passed
its 10s TTL, so lock timing does not change.

The shared helpers now take only the three lock operations, so ACP's
narrow state type fits. The keep-alive extend now ignores a rejected
call, as the ACP copy did, and the next fenceLock still catches a lost
lock.

Co-Authored-By: David Cramer <david@sentry.io>
…force

A private item now links only to the Conversation that added it, so every
linked thread can show its Slack card.

Co-Authored-By: David Cramer <david@sentry.io>
Main now rejects parsing a Conversation id to find a Slack thread. An
Automation run can own a Slack thread with an id that is not a Slack
thread id. The card sync now gets the thread from resolveSlackReplyTarget
and skips a Conversation without a thread. Cards are Block Kit controls
that change in place, so the sync joins the raw-post rule ignores, like
the requestUserInput controls.

Co-Authored-By: David Cramer <david@sentry.io>
…ate content off public items

The agent tools read items as the Actor too. An assigned User could then
read a private item, or an item of another workspace, from any
Conversation, and the agent could copy it there. The tools now read as
the Conversation. The review desk still shows assigned items.

An agent can now change an item only in its workspace, and a public item
only from a public Conversation, so a private run cannot write private
content into a public item.

Co-Authored-By: David Cramer <david@sentry.io>
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

3 screenshot changes — 3 changed · 0 added · 0 removed

Review screenshots in Frameshift

Conversation Child Agent Drawer · Desktop
Conversation Child Agent Drawer · Desktop
Changed
Conversation Child Agent Drawer · Mobile
Conversation Child Agent Drawer · Mobile
Changed
Location Detail · Desktop
Location Detail · Desktop
Changed

Slack cards now name their Conversation from the card row, so a click in
a thread that an Automation run owns records Slack input in that run and
excludes it from fan-out. Choice buttons carry their label, and a click on
a stale card whose label the item no longer offers is rejected. A closed
card puts its note and review desk link in separate context elements, so a
long answer stays inside the Slack 3,000-character limit.

A retry of a take, or of the same agent close, now sends the messages that
a failed earlier attempt did not send. Each message has a key, so only the
missing ones go.

A private item that an Automation added can be assigned to the creator of
that Automation. A fresh run often has no Actor and no people yet.
@blacksmith-sh

This comment has been minimized.

@dcramer
dcramer merged commit 12de614 into main Oct 31, 2026
63 of 65 checks passed
@dcramer
dcramer deleted the feat/review-items-in-slack branch October 31, 2026 04:13

This branch was successfully deployed

1 active deployment
Preview – junior-docs — 42549eff Deployed Oct 31, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: high PR risk score: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant