tracecathq/tracecat/slack-case-threads

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Load when the prompt is a raw Slack payload, either an Events API envelope or an Interactivity payload. Covers how Socky handles an app_mention (acknowledge, read the thread from Slack, answer, record on the case, mark done) and the owner's Yes/No buttons on a GuardDuty case. Conversation history is read from Slack with list_replies and list_messages, never from stored state. For a mention, load aws-cloud-incident-response-core and case-output before answering anything that needs evidence. For the owner's Yes/No buttons, load nothing else, because this skill holds every rule that path needs.

Install

npx vskill@latest install tracecathq/tracecat/slack-case-threads

SKILL.md

Slack case threads

The workflow hands you whatever Slack sent, unchanged, as JSON. The Slack app subscribes only to app_mention events and to interactivity, so your own posts never come back to you. Read the payload, decide which of the cases below it is, and handle exactly that one. When you are done, return one plain line for the run record saying what you did.

Read the payload

PayloadWhat it isSection
type is url_verificationSlack checking the Request URLReturn url_verification: nothing to do.
payload is a JSON string whose type is block_actions and whose actions[0].action_id is gd_confirm_yes or gd_confirm_noThe owner answered the askOwner buttons
payload is any other JSON stringAnother button or view on a bot messageReturn Interaction <type> <action_id>: not handled.
type is event_callback and event.type is app_mentionSomeone mentioned the botMentions
event.bot_id is set, or event.user equals authorizations[0].user_idA bot, or youReturn Ignored a bot event.
anything elseNot for youReturn Ignored <type>/<event.type>.

Work out payload with a JSON parse of the string. Slack form-encodes interactivity, so the string is the whole interaction.

Mentions

  1. Acknowledge. Nothing to do. The workflow's ack_mention has already added the eyes reaction to the mention. Add no reaction yourself.
  2. Read the conversation from Slack. The thread is event.thread_ts, or event.ts when the mention starts a new thread.
  • Call tools.slack.list_replies on the channel and that thread for the whole conversation, root first.
  • When the question refers to something said elsewhere in the channel, call tools.slack.list_messages on the channel with oldest and latest bounding the time you need.
  1. Find the case.
  • Call core.table.lookup on slack_work_objects with column message_ts and the thread's ts.
  • Failing that, take the case id from a cases/<uuid> link in the root message.
  • With a case id, call core.cases.get_case for its description, fields and tags.
  1. Answer as case-output's "Thread replies" describes:
  • one reply in the thread;
  • one Slack: case comment;
  • at most one direct message, under the rules there.

For an investigative question, fetch the case's findings with boto3 as guardduty-case-lifecycle stage 1 describes. Test the question in the SIEM with the method in hypothesis-driven-triage.

  1. Outside a case thread (no case found): answer helpfully in the thread with the same evidence rules. Add no case comment and change nothing on any case.
  2. Mark done. Nothing to do. When your run finishes, the workflow's mark_done adds white_check_mark and clear_eyes then removes eyes, so the reader is left with a single green tick. Add and remove no reaction yourself. When your run fails, the eyes stay.

A mention never changes a case's status, severity, disposition or tags, whatever it asks. A request to contain, disable, revoke, block, close or change the status gets the reply case-output prescribes, and nothing else.

Owner buttons

This path is timed: the person who pressed is watching the ask. Everything the first visible write needs is already in payload, so load no other skill, read no bundled file, and make no call before the first write turn. Do not call get_case, list_replies or a shell or Python tool first. The workflow's ack_owner_click has already replaced the buttons with :hourglass_flowing_sand: <@USER> answered Yes. Socky is recording it. (or No); your first write swaps that for the final status line.

Read everything from payload
NameValue
case idactions[0].value
short id, ownerSplit actions[0].block_id on :. Current asks read gd_confirm:<short id>:<owner user id>:<owner email>:<owner name> (the name is everything after the fourth colon). Legacy asks read gd_confirm:<short id> or plain gd_confirm; with no short id, write the case wherever <short id> appears below
personWith an owner in the block_id: <owner name> (<owner email>), for example Jane Doe (jane.doe@example.com), never a <@…> mention. The workflow only lets the owner's own click through, so on behalf never applies. Without an owner (a legacy ask): <@USER> as before, with on behalf below
useruser.id; name is user.name, else user.username, else user.id
deniedactions[0].action_id is gd_confirm_no
on behalfLegacy asks only: message.text does not contain <@user.id> (someone other than the person tagged pressed it)
channelcontainer.channel_id, else channel.id
ask tscontainer.message_ts, else message.ts
thread (card ts)message.thread_ts
press timeactions[0].action_ts, epoch seconds, written DD Mon YYYY, HH:MM UTC. Work it out yourself; never call a tool to format it. 1790726400 is 30 Sep 2026, 00:00 UTC, a day is 86400 s, an hour 3600 s. Example: 1790730000 is 30 Sep 2026, 01:00 UTC
press epochint(actions[0].action_ts): the whole seconds, no decimal part, for example 1790730000. Used for metadata_last_modified

Duplicate. The workflow's claim_event is the primary guard: it lets only the first answer per case reach you. As a secondary guard, when message.blocks already holds a context block starting :white_check_mark: or :x:, the answer is recorded. Change nothing, post nothing, and return Duplicate click on <short id>: already recorded.

Slack formatting. Every edit and post here passes text (and blocks where given) in Slack mrkdwn, single asterisks for bold. Never pass markdown_text: Slack rejects a call carrying both (markdown_text_conflict), and markdown_text alone renders *Verdict:* in italics.

Turn 1: all of these in parallel, in one turn
CallYes (gd_confirm_yes)No (gd_confirm_no)
tools.slack.update_message on channel and ask ts: blocks = every block of message.blocks except the actions block and any context block, plus one context block (mrkdwn) with the status line; text as shownstatus line Yes, exactly as in "Exact text" below; text <person> confirmed this activity. The case is set to Benign.status line No, exactly as in "Exact text" below; text <person> denied this activity. The case is set to Escalate for a person to decide.
tools.slack.post_message in the thread, unfurl_links false (thank-you)<@USER>, thank you for confirming this activity<on behalf>. <short id> is now Benign, and the security team will review and close it.<@USER>, thank you for telling us this activity was not expected<on behalf>. <short id> is now Escalate: the security team will treat it as a possible credential compromise and will be in touch. You do not need to do anything else right now.
core.cases.add_case_tag, create_if_missing trueowner-confirmedowner-denied, and a second call for escalation
core.cases.update_casefield disposition Benign, severity lowfield disposition Escalate, severity critical
core.cases.create_commentSlack: <person> confirmed the activity from the Slack thread<on behalf of the tagged person> (button gd_confirm_yes). Disposition set to Benign, severity low. Owner confirmation recorded.Slack: <person> denied the activity from the Slack thread<on behalf of the tagged person> (button gd_confirm_no). Disposition set to Escalate, severity critical, escalation tag added; a person decides containment.
tools.slack.list_replies on channel and thread (read, for turn 2)samesame
core.table.lookup on slack_work_objects, column external_ref_id, value case id (read, for turn 2)samesame
Exact text

The press time is always wrapped in backticks (Slack inline code) in the status line and the Verdict line. Copy these lines character for character, replacing only the angle-bracket placeholders. With block_id gd_confirm:CASE-0001:U0123456789:jane.doe@example.com:Jane Doe and 30 Sep 2026, 01:00 UTC as the press time, the Yes status line and Verdict line are exactly:

:white_check_mark: *Confirmed by* Jane Doe (jane.doe@example.com) at `30 Sep 2026, 01:00 UTC`. The case was set to Benign and a comment was added to CASE-0001.
*Verdict:* Benign. Jane Doe (jane.doe@example.com) confirmed this activity at `30 Sep 2026, 01:00 UTC`.

Slack mentions appear only in the thank-you (and in the brief's step 1, which you leave unchanged).

The templates:

Yes status line: :white_check_mark: *Confirmed by* <person><not tagged> at `<press time>`. The case was set to Benign and a comment was added to <short id>.
No status line: :x: *Denied by* <person><not tagged> at `<press time>`. The case was set to Escalate and a comment was added to <short id>.
Yes Verdict line: *Verdict:* Benign. <person> confirmed this activity at `<press time>`.
No Verdict line: *Verdict:* Escalate. <person> denied this activity at `<press time>`; treated as a possible credential compromise.

(The labels before the colon and the spaces after them are not part of the text.)

<not tagged> is (not the person tagged above), <on behalf> is on behalf of the person tagged above, and <on behalf of the tagged person> is on behalf of the tagged person, each only when on behalf applies, else nothing.

Turn 2: brief, card and row, in parallel
  • Brief. In the list_replies result, the brief is the bot's reply whose text starts *Verdict:*. Replace its first paragraph and keep every paragraph after it unchanged, then save it with tools.slack.update_message, passing the whole brief as text with the blank lines between paragraphs:
  • Yes: the Yes Verdict line from "Exact text".
  • No: the No Verdict line from "Exact text".
  • Card. Take the row's entity. Set the verdict custom field to Benign · owner confirmed or Escalate · owner denied, and entity_payload.attributes.metadata_last_modified to the press epoch, an integer (int(action_ts), never the decimal action_ts; it must be greater than the stored value). The row gets the same entity. Call tools.slack_sdk.call_method with sdk_method chat_update and params channel = the row's channel, ts = the row's message_ts, text " " (one space: chat_update rejects a missing or empty text with no_text, and any other text shows above the card), and metadata {"entities": [<entity plus "app_unfurl_url": <entity url>>]}.
  • Row. core.table.insert_row with upsert true, table slack_work_objects, and row_data with exactly these five columns: external_ref_id (case id), case_id, channel, message_ts, entity (the updated entity, without app_unfurl_url). Never send updated_at: it fails with "Column 'updated_at' does not exist".

When the thread has no brief or the table has no row, skip that write and say so in the return line. Only then, and only when a later write needs something the payload lacks, call core.cases.get_case on the case id.

Return <short id> confirmed by <name>: Benign, low. or <short id> denied by <name>: Escalate, critical.

Security

  • Security scanPassed, 90 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/tracecathq/tracecat/slack-case-threads/badge)](https://verified-skill.com/skills/tracecathq/tracecat/slack-case-threads)

Something wrong with this skill or its page? Tell us.