PerfCopilot
HomeDocs
Documentation

Everything you need to write a fair review.

From your first connection to a sealed, delivered review. Set up PerfCopilot, connect your tools, and generate your first cited draft.

Slack

Slack captures collaboration that doesn't show up in tickets or commits: helping teammates unblock, staying responsive to mentions, and maintaining a consistent presence in the channels where work happens. The headline metric is help_replies_given — replies in threads that match help-seeking patterns.

What we pull

The ingester scans every public channel the Slack app has access to and records per-message metadata for each employee. No message text is stored. What's stored per event:

  • is_thread_reply — whether the message is a reply in an existing thread
  • is_help_reply — whether the reply landed in a thread that was a help request. Detection looks at the parent message of the thread (keywords like "anyone know", "stuck", "blocked on", "can someone", "how do I", etc.) OR at the channel name — any reply in a channel matching the help-channel pattern (default ^help-, case-insensitive) counts.
  • positive_reactions — count of positive reactions the message received (+1, heart, fire, rocket, 100, etc.)
  • reaction_count — total reactions
  • has_mention — whether the message @-mentions someone
  • is_business_hours — whether the message was sent 9AM–6PM Mon–Fri in the org's timezone

A separate response time analysis runs via search.messages, measuring how quickly the employee responds to @mentions during business hours. This produces avg_minutes, median_minutes, p90_minutes, and under_15min_pct.

Public channels only. Private channels and DMs are never accessed. This is hard-coded and not configurable.

Connecting

Slack is an org-wide install.

  1. Go to /admin?tab=integrations, find the Slack card.
  2. Click Connect. You'll authorize the PerfCopilot Slack app for your workspace.
  3. The app requires channels:history, channels:read, users:read, and search:read scopes. The OAuth flow requests all of these.
  4. Map each employee's Slack member ID (visible in their Slack profile — click the three dots, then "Copy member ID") in the unmapped-employees list on the card.

User token for search. search.messages requires a user-scoped OAuth token. PerfCopilot's OAuth flow requests this alongside the bot token. Legacy PAT-based installs used the PAT as both, which works but is less precise. If you installed before 2026-05-01, reconnecting will pick up the user token.

Optional: help-channel pattern

The Slack card on /admin?tab=integrations exposes one optional setting: Help channel pattern (regex). Any reply in a channel whose name matches this regex counts as a help reply regardless of the parent message's wording — useful for dedicated rooms like #help-eng, #ask-platform, etc.

  • Default (blank): ^help- (case-insensitive). So #help-eng, #help-data, #Help-Frontend all match.
  • Custom example: ^(help|ask)[-_] to also cover #ask-eng.
  • Invalid regex falls back to the default silently — the ingester logs a warning but doesn't abort.

Replies outside matching channels still count when the thread's parent message contains help-seeking keywords. The two arms are OR'd.

What hits a review

[SLACK DATA]
total_messages:       94
help_replies_given:   18
positive_reactions:   41
avg_response_min:     11.2
under_15min_pct:      73
channels_active:       6
business_hours_pct:   88

Plus cohort medians for help_replies_given and avg_response_min in the [BASELINES] block.

Troubleshooting

"Slack signals show zero messages"

  1. No Slack member ID mapped. The ingester filters channel history by user ID. Without it, every message in every channel is skipped. Check the Slack card for unmapped employees.
  2. App not invited to channels. The bot must be a member of a channel to read its history. Use /invite @PerfCopilot in the channels you want tracked, or install the app to all channels from Slack's App Directory page.
  3. All channels are private. The ingester only reads public channels. If your org uses only private channels, Slack signals will always be zero.

"Response time shows no data"

search.messages requires a user-scoped token. If the card shows response_stats: {}, it means the search step ran with a bot-only token. Reconnect the Slack integration to pick up the user token.

"Help replies count seems too high / too low"

The is_help_reply flag is set when either:

  1. The thread's parent message contains help-seeking keywords (the ingester fetches the parent once per thread and caches it, so a 50-reply thread costs one extra API call, not fifty), or
  2. The reply landed in a channel matching your help channel pattern (default ^help-).

If counts look off:

  • Too high in a busy #help-* room? The channel arm fires on every reply in that channel — that's the design. Narrow the regex if you only want certain rooms (e.g. ^help-(eng|data)$).
  • Too low in a non-help channel? The keyword arm matches the literal parent text. If your team phrases questions in a way none of the default keywords catch ("Anybody ran into…?"), open a support ticket — we can extend the keyword set or you can route those threads into a channel that matches your help pattern.

Privacy notes

  • Message text is never stored. Metadata only.
  • positive_reactions counts are stored but individual reaction names are not linked back to who reacted.
  • Business hours filtering uses the org timezone set in Settings → Organization. If that's not set, UTC is assumed, which may misclassify messages near 9AM/6PM boundaries.
  • The ingester limits Slack API concurrency to 5 parallel channel scans to stay within Slack's per-workspace rate limits.