slack

SKILL.md

Slack Integration Skill

This skill provides workflows for interacting with Slack via a self-contained Python client. It supports multiple workspaces with contextual auto-selection, and four primary workflows: sending messages, reading notifications, generating activity digests, and retrieving sent message history.

Setup

First-Time Setup Flow

If config.json is missing when running a Slack command, walk the user through setup:

  1. Create config file:

    cd ~/.claude/skills/slack
    cp config.example.json config.json
    
  2. Guide user to extract browser tokens:

    • Open Slack in browser and log into the desired workspace
    • Open Developer Tools (F12 or Cmd+Option+I)
    • Get xoxc_token by running in Console tab:
      JSON.parse(localStorage.localConfig_v2).teams[document.location.pathname.match(/^\/client\/([A-Z0-9]+)/)[1]].token
      
    • Get xoxd_token from Application tab:
    • Get user_agent by running in Console tab: navigator.userAgent
    • Note the workspace name from the URL (e.g., hartreeworks from hartreeworks.slack.com)
  3. Ask for preferences using AskUserQuestion:

    question: "How would you like Slack links to open?"
    header: "Link style"
    options:
      - label: "Browser"
        description: "Open links in web browser"
      - label: "Native app"
        description: "Open links in Slack desktop app"
    multiSelect: false
    
  4. Add the workspace using the CLI:

    SCRIPT=~/.claude/skills/slack/scripts/slack_client.py
    python3 $SCRIPT add-workspace "workspace-name" "xoxc-token" "xoxd-token" "user-agent"
    
  5. Test the connection:

    python3 $SCRIPT auth
    

Adding Additional Workspaces

To add another workspace, repeat the token extraction for the new workspace and run:

python3 $SCRIPT add-workspace "new-workspace" "xoxc-token" "xoxd-token"

The user_agent is optional when adding subsequent workspaces (defaults to first workspace's value).

Config File Structure

config.json supports multiple workspaces:

{
  "workspaces": {
    "hartreeworks": {
      "xoxc_token": "xoxc-...",
      "xoxd_token": "xoxd-...",
      "user_agent": "Mozilla/5.0..."
    },
    "another-workspace": {
      "xoxc_token": "xoxc-...",
      "xoxd_token": "xoxd-...",
      "user_agent": "Mozilla/5.0..."
    }
  },
  "default_workspace": "hartreeworks",
  "link_style": "app"
}
Field Required Description
workspaces Yes Object keyed by workspace name
workspaces.*.xoxc_token Yes Browser token from localStorage
workspaces.*.xoxd_token Yes Browser cookie token
workspaces.*.user_agent Yes Your browser's User-Agent string
default_workspace Yes Fallback workspace when none specified
link_style Yes "app" (native Slack) or "browser" (web browser)

Test the Connection

python3 ~/.claude/skills/slack/scripts/slack_client.py auth

CRITICAL: User ID Resolution

Slack API returns user IDs (e.g., U02GYLM0A), NOT display names. You MUST resolve these IDs to names before presenting any Slack content to the user.

Why This Matters

Guessing names from context is a critical failure mode. User IDs like U7DTUK3U6 give no indication of who the person is. If you summarize a thread and attribute quotes to the wrong people, you're spreading misinformation.

Mandatory Workflow

Before summarizing ANY Slack content containing user IDs (threads, messages, search results):

  1. Get the user lookup for the workspace:

    python3 $SCRIPT -w <workspace> user-lookup
    

    This returns a JSON mapping of user_id → display_name.

  2. Resolve all user IDs in the content using the lookup before presenting to the user.

That's it - just two steps. The user-lookup command handles caching automatically:

  • First time: Fetches users from Slack API (may take a few seconds)
  • Cache stale (>14 days): Returns cached data immediately, refreshes in background
  • Cache fresh: Returns cached data instantly

User Resolution Commands

Command Purpose
user-lookup Get user_id → display_name mapping (auto-fetches if empty, background-refreshes if stale)
fetch-users Force refresh the user cache from Slack API

Example

# 1. Get user lookup FIRST (handles caching automatically)
python3 $SCRIPT -w 80000hours user-lookup
# Returns: {"U02GYLM0A": "Benjamin Todd", "U7DTUK3U6": "Niel", ...}
# (First time: fetches from API. Later: uses cache, refreshes in background if >14 days old)

# 2. Fetch a thread
python3 $SCRIPT -w 80000hours replies "C039MDQ91" "1767837883.421009"
# Returns messages with user IDs like "U02GYLM0A", "U7DTUK3U6"

# 3. Now you can correctly attribute: "Benjamin Todd said..." not "User U02GYLM0A said..."

What NOT To Do

  • ❌ Guess names based on context clues in messages
  • ❌ Assume you know who someone is from their writing style
  • ❌ Present a summary with user IDs instead of names
  • ❌ Skip the lookup step because "it's just one message"

What To Do

  • ✅ Always run user-lookup before summarizing Slack content (it handles caching automatically)
  • ✅ If a user ID isn't in the lookup, run fetch-users to force refresh, or show the ID with a note

Python Client Commands

The scripts/slack_client.py script provides these commands. All commands support an optional -w <workspace> flag to specify the workspace.

Core Commands

Command Arguments Purpose
auth - Test authentication, get user info
channels [types] List channels (default: all types)
users - List all workspace users
user-lookup - Get user_id → display_name mapping (auto-fetches/refreshes as needed)
fetch-users - Force refresh user cache from Slack API
history channel_id [limit] Get message history
replies channel_id thread_ts Get thread replies
search query [count] Search messages
send channel_id text [thread_ts] Send a message
permalink channel_id message_ts [workspace] Get message permalink

Workspace Management Commands

Command Arguments Purpose
workspaces - List configured workspaces
switch workspace_name Set active workspace
add-workspace name xoxc xoxd [user_agent] Add a new workspace

Example Usage

SCRIPT=~/.claude/skills/slack/scripts/slack_client.py

# List configured workspaces
python3 $SCRIPT workspaces

# Switch active workspace
python3 $SCRIPT switch acme-corp

# Use specific workspace for one command
python3 $SCRIPT -w hartreeworks channels

# List public channels
python3 $SCRIPT channels "public_channel"

# Search for messages
python3 $SCRIPT search "from:@username after:2025-01-01" 50

# Send a message
python3 $SCRIPT send "C0123456789" "Hello world!"

# Send a thread reply
python3 $SCRIPT send "C0123456789" "Thread reply" "1234567890.123456"

# Get channel history
python3 $SCRIPT history "C0123456789" 20

# Get message permalink
python3 $SCRIPT permalink "C0123456789" "1234567890.123456"

Workspace Selection

The skill supports multiple workspaces with contextual auto-selection.

Selection Priority

When a command runs without -w, the workspace is selected in this order:

  1. Explicit flag: -w workspace-name always wins
  2. Channel context: If operating on a channel known to belong to a workspace
  3. Recent activity: Active workspace from the last 10 minutes
  4. Default workspace: Configured in config.json
  5. First workspace: If nothing else matches

Handling Ambiguous Workspace

When the workspace is ambiguous (e.g., user asks to "send a message to #general" but multiple workspaces have a #general channel), use AskUserQuestion:

question: "Which Slack workspace should I use?"
header: "Workspace"
options:
  - label: "hartreeworks"
    description: "hartreeworks.slack.com"
  - label: "acme-corp"
    description: "acme-corp.slack.com"
multiSelect: false

Then pass the selected workspace using the -w flag.

Session State

The skill tracks workspace context in session-state.json:

  • active_workspace: Most recently used workspace
  • workspace_channel_map: Maps channel IDs to their workspaces

This enables automatic workspace inference when operating on previously-seen channels.

Performance Cache

Each workspace has its own cache file (slack-cache-{workspace}.json) storing frequently-used IDs.

Using the Cache

Before making API calls to look up users or channels:

  1. Read slack-cache-{workspace}.json for the current workspace
  2. Check if the needed ID is already cached
  3. If found, use the cached value directly
  4. If not found, make the API call, then update the cache

Cache Structure

{
  "user": {"id": "...", "username": "...", "display_name": "..."},
  "self_dm_channel": "D...",
  "workspace": "hartreeworks",
  "frequent_contacts": {"username": {"id": "...", "display_name": "..."}},
  "channels": {"#channel-name": "C..."}
}

Updating the Cache

After successful lookups, add new entries:

  • New user lookups → add to frequent_contacts
  • New channel lookups → add to channels
  • Update last_updated date

On lookup errors (user not found, channel not found), the cached entry may be stale - remove it and retry.

Workflow 1: Send a Message

To a Channel

  1. Find the channel ID (check cache or list channels):

    python3 $SCRIPT channels "public_channel,private_channel"
    
  2. Send the message:

    python3 $SCRIPT send "C0123456789" "Your message here"
    

To a Thread

  1. Get the thread's parent message timestamp (ts)
  2. Send the reply:
    python3 $SCRIPT send "C0123456789" "Thread reply" "1234567890.123456"
    

To a DM

  1. Find the DM channel ID (check cache or use @username):

    python3 $SCRIPT channels "im"
    
  2. Send the message:

    python3 $SCRIPT send "D0123456789" "Your DM message"
    

Message Formatting

Messages support Slack's mrkdwn format:

  • *bold* for bold text
  • _italic_ for italic text
  • ~strikethrough~ for strikethrough
  • `code` for inline code
  • <@USER_ID> for user mentions
  • <#CHANNEL_ID> for channel mentions

Tables in Slack

IMPORTANT: When including tables in Slack messages, always wrap them in triple backticks (code blocks). Slack uses a proportional font by default, so table columns won't align properly without monospace formatting.

Correct format:

*Summary Title*

\`\`\`
| Metric      | Score |
|-------------|-------|
| Quality     | 4.5/5 |
| Usefulness  | 4.2/5 |
\`\`\`

More text here...

Why this matters: Without code blocks, pipe characters and spacing won't align, making tables unreadable.

Workflow 2: Read Recent Activity

To check what the user missed or review recent activity:

  1. Get recent messages from relevant channels:

    python3 $SCRIPT history "C0123456789" 50
    
  2. For each channel of interest, summarize:

    • New messages since last check
    • Mentions of the user
    • Important threads that need attention
  3. Search for messages mentioning the user:

    python3 $SCRIPT search "<@USER_ID>" 20
    

Workflow 3: Slack Activity Digest

When the user asks for a "Slack digest" or "activity summary", use the AskUserQuestion tool to prompt for the time period:

Time Period Selection

Note: Slack search only supports date-based queries (after:YYYY-MM-DD), not datetime. Options are designed around this limitation.

Present these options using AskUserQuestion:

Option Search Query Notes
Today only from:@username after:YYYY-MM-DD (today's date) Messages from today
Since yesterday from:@username after:YYYY-MM-DD (yesterday's date) Yesterday + today
Last 7 days from:@username after:YYYY-MM-DD (7 days ago) Full week
Last calendar week from:@username after:YYYY-MM-DD before:YYYY-MM-DD Mon-Sun of previous week

Generating the Digest

  1. Search for user's sent messages in the selected period:

    python3 $SCRIPT search "from:@username after:2025-01-01" 100
    
  2. Analyze messages and group by theme/conversation:

    • Identify main topics and projects discussed
    • Group related messages together
    • Note key people involved in each thread
  3. Build the message index as you analyze:

    • Assign each referenced message a numbered ID (1.1, 1.2, 2.1, etc.)
    • First number = theme/section, second number = item within section
    • Store in last-digest.json (see format below)
  4. Present digest using numbered lists (not bullets):

## Your Slack Activity Digest: [Date Range]

### 1. [Theme/Project Name]

1.1. [Brief description of message/activity]
1.2. [Another message in this theme]
1.3. [Key decision or outcome]

People: [names involved]

### 2. [Theme/Project Name]

2.1. [Brief description]
2.2. [Another item]

People: [names involved]

### 3. Misc

3.1. [One-off message]
3.2. [Another minor item]

---

**Stats:** ~X messages | Channels: [list] | Busiest: [days]

💡 Say "open 1.2" to view any message in Slack

Message Index File

After generating the digest, write ~/.claude/skills/slack/last-digest.json:

{
  "generated": "2025-01-15T14:30:00Z",
  "period": "2025-01-13 to 2025-01-15",
  "workspace": "hartreeworks",
  "messages": {
    "1.1": {"channel": "C04AFNMCNFP", "ts": "1736789012.123456"},
    "1.2": {"channel": "C04AFNMCNFP", "ts": "1736789100.654321"},
    "2.1": {"channel": "D18U650RY", "ts": "1736801234.111111"},
    "3.1": {"channel": "C02ABC123", "ts": "1736812345.222222"}
  }
}

Opening Messages

When user says "open 1.2" or "open message 2.1":

  1. Read config.json to get link_style preference
  2. Read last-digest.json and look up the message reference (includes workspace)
  3. Generate permalink using the permalink command with the user's link_style:
    # For link_style: "app" (default)
    python3 $SCRIPT permalink "C04AFNMCNFP" "1736789100.654321" "hartreeworks" "app"
    # Returns: https://hartreeworks.slack.com/archives/C04AFNMCNFP/p1736789100654321
    
    # For link_style: "browser"
    python3 $SCRIPT permalink "C04AFNMCNFP" "1736789100.654321" "hartreeworks" "browser"
    # Returns: https://hartreeworks.slack.com/messages/C04AFNMCNFP/p1736789100654321
    
  4. Open the link:
    open "<permalink>"
    

Example AskUserQuestion Call

Use AskUserQuestion with:
- question: "What time period would you like the Slack digest for?"
- header: "Time period"
- options:
  - label: "Today only"
    description: "Messages from today"
  - label: "Since yesterday"
    description: "Yesterday and today"
  - label: "Last 7 days"
    description: "Messages from the past week"
  - label: "Last calendar week"
    description: "Monday to Sunday of last week"
- multiSelect: false

Workflow 4: Get Sent Messages

To retrieve messages the user sent during a specific period:

  1. Search for the user's messages:

    python3 $SCRIPT search "from:@username" 50
    
  2. Filter by date range if specified:

    python3 $SCRIPT search "from:@username after:2025-01-01 before:2025-01-31" 100
    
  3. Present results grouped by channel or date.

Common Search Queries

Query Purpose
from:@username All messages from user
from:@username in:#channel Messages in specific channel
from:@username after:YYYY-MM-DD Messages after date
from:@username before:YYYY-MM-DD Messages before date
from:@username has:link Messages containing links
from:@username has:reaction Messages with reactions

Workflow 5: Export messages archive

Export the user's sent messages with full thread context to a JSON file. Supports resume if interrupted.

Basic export

SCRIPT=~/.claude/skills/slack/scripts/slack_client.py

# Export last 6 months of messages
python3 $SCRIPT export --from 2025-07-01 --to 2026-01-05 --output ~/slack-export.json

# Export for a specific workspace
python3 $SCRIPT -w 80000hours export --from 2025-07-01 --to 2026-01-05 --output ~/slack-export.json

Resume an interrupted export

If the export is interrupted (Ctrl+C or error), resume from where it left off:

python3 $SCRIPT export --resume

Check export status

python3 $SCRIPT export-status

How it works

The export runs in three phases:

  1. Search phase: Searches for all messages sent by the user in the date range using paginated search
  2. Thread fetch phase: For each thread the user participated in, fetches the complete thread (including messages from others) for context
  3. Write phase: Outputs a JSON file with all data

Rate limiting

The export respects Slack's rate limits:

  • ~45 search requests per minute (Tier 3)
  • ~90 thread fetch requests per minute (Tier 4)
  • Automatic backoff if rate limited

Output format

The JSON export contains:

{
  "metadata": {
    "workspace": "80000hours",
    "user": {"id": "U...", "username": "pete.hartree"},
    "date_range": {"from": "2025-07-01", "to": "2026-01-05"},
    "exported_at": "2026-01-05T12:00:00Z",
    "stats": {
      "total_messages": 2847,
      "total_threads": 423,
      "standalone_messages": 312,
      "channels_count": 45
    }
  },
  "channels": {
    "C0123456789": {"id": "...", "name": "general", "type": "channel"}
  },
  "threads": [
    {
      "thread_id": "C0123456789:1735000000.111111",
      "channel_id": "C0123456789",
      "user_message_count": 3,
      "total_message_count": 12,
      "messages": [
        {"ts": "...", "user": "U...", "text": "...", "is_user_message": true}
      ]
    }
  ],
  "standalone_messages": [
    {"ts": "...", "channel_id": "...", "text": "...", "is_user_message": true}
  ]
}

Scale estimates

For 6 months of active usage:

  • ~3,000 messages → ~30 search API calls
  • ~800 unique threads → ~800 thread API calls
  • Total time: 15-30 minutes with rate limiting
  • Output file: 10-20 MB

Working with Threads

Fetch Thread Replies

python3 $SCRIPT replies "C0123456789" "1234567890.123456"

Reply to a Thread

python3 $SCRIPT send "C0123456789" "Your reply" "1234567890.123456"

Channel Discovery

List All Channels

python3 $SCRIPT channels "public_channel,private_channel"

List DM Conversations

python3 $SCRIPT channels "im,mpim"

List All Users

python3 $SCRIPT users

Error Handling

Common Issues

Error Cause Solution
"ok": false API error Check the error field in response
invalid_auth Token expired Extract fresh tokens from browser
channel_not_found Invalid channel ID List channels to verify
not_in_channel User not a member Join channel first

Token Expiry

Browser tokens (xoxc/xoxd) expire periodically. If requests fail with auth errors:

  1. Extract fresh tokens from browser
  2. Update config.json
  3. Test with python3 $SCRIPT auth

Known Limitations

No Activity Feed / Unread Notifications

The Slack Activity feed API is not available. Cannot directly fetch:

  • Unread notification count
  • Reactions to user's messages

Workarounds:

  • Search for messages mentioning the user's ID: <@USER_ID>
  • Search for recent activity in user's DMs
  • Check specific channels for recent messages

Additional Resources

Reference Files

For detailed API response formats:

  • references/tools-reference.md - Complete API documentation

Token Setup

For extracting browser tokens:

  • references/setup-guide.md - Step-by-step token extraction guide
Weekly Installs
2
First Seen
Feb 8, 2026
Installed on
opencode2
claude-code2
kiro-cli2
gemini-cli2
continue1
mcpjam1