Slack thread export
Use this skill when Slack data must be exported from a real logged-in browser tab instead of a bot token or admin API.
Scope and non-goals
This skill is intentionally optimized for logged-in Slack Web exports through an attached Chrome Browser Relay tab.
It is meant to be good at:
- - exporting thread replies for one user across selected channels
- building CSV/JSONL archives from Slack search
- narrowing results by channel/date and optionally reducing non-work noise
- surviving partial failures with resumable raw output and failed-channel retries
It is not meant to be:
- - a generic Slack admin/export replacement
- a bot-token or SCIM-based export tool
- a guaranteed complete legal/compliance archive
- a universal classifier for "work-related" messages
Workflow
- 1. Ensure the user has attached the Slack tab with Browser Relay and use
profile="chrome". - Use the browser tool or
openclaw browser --browser-profile chrome to confirm the attached tab is the correct Slack workspace. - Read
localStorage.localConfig_v2 from the Slack tab to get the active team metadata and xoxc token used by the web client. - Query Slack's internal
search.messages endpoint from inside the page context using fetch('/api/search.messages', ...) so the logged-in browser session supplies the right cookies/session context. - Export in small batches:
- Prefer channel-by-channel queries over one huge query.
- Prefer page-by-page loops (
count=100, increment
page).
- Back off on
ratelimited with sleep/retry.
- Stop when
count == 0 or
< 100 for a page.
- 6. Exclude unwanted records before writing files:
- Drop IM/MPIM results.
- Keep only records whose permalink includes
thread_ts= when the user wants thread replies.
- Apply user-requested business filters (channel whitelist, keyword filter, date range, etc.).
- 7. Save both:
- raw JSONL for audit/debugging
- cleaned CSV for the user's actual deliverable
How it works
This skill does not scrape visible Slack DOM rows as the primary path. Instead it piggybacks on the same internal search flow used by Slack Web:
- 1. Attach to the user's already logged-in Slack browser tab.
- Read the active workspace metadata from
localStorage.localConfig_v2. - Reuse the web client's xoxc token inside the page context only.
- Call
fetch('/api/search.messages', ...) from the attached Slack page so the request inherits the browser session cookies and client context. - Page through Slack search results and transform them into exportable rows.
This is more reliable than host-side requests because Slack search often expects both the token and the live logged-in browser session.
Operational numbers and limits
Use these defaults unless there is a clear reason not to:
- -
count=100 per search.messages request
- This is the practical max batch size used by the web client path here.
- -
sleep-seconds=0.5 between normal page requests - On
ratelimited, sleep at least 3-5 seconds before retrying the same page - Start with
max-pages=60 per channel as a practical ceiling - Prefer 1 channel at a time or a short whitelist over a whole-workspace sweep
Practical guidance:
- - Usually safe: 1 user + 1 channel + 1-20 pages
- Still reasonable: 1 user + 3-10 channels + paging with 0.5s delay
- Risky: one giant all-history query across the whole workspace
- Very risky: one huge page-context evaluate that loops many channels and many pages in one call
Why this matters:
- - Slack will return
ratelimited if requests come too quickly or the query is too broad. - Browser evaluation can time out if too much work is packed into one evaluate call.
- The safest shape is many short calls, not one giant call.
What was actually validated
These are not theoretical claims only; the workflow was exercised against a real logged-in Slack tab.
Small-range validation
Test shape:
- - channels:
tech-team, moonlight, INLINECODE21 - range:
after=2026-03-01, INLINECODE23 - mode: INLINECODE24
Observed result:
- -
tech-team: 9 rows - INLINECODE26 : 29 rows
- INLINECODE27 : 0 rows
- total raw: 38
- unique raw: 38
- failed channels: 0
- total requests: 3
- duration: ~5.05s
This validated:
- - INLINECODE28
- INLINECODE29
- INLINECODE30
- INLINECODE31
- INLINECODE32
- INLINECODE33
- INLINECODE34
- INLINECODE35 mode
Heuristic filtering validation
To prove that heuristic mode does more than strict, a second test included edge channels:
- - INLINECODE38
- INLINECODE39
- INLINECODE40
- INLINECODE41
- INLINECODE42
Observed result:
- -
strict: 50 rows kept - INLINECODE44 : 40 rows kept
- removed by heuristic: 10 rows
-
random: 9
-
music: 1
The removed examples were casual/non-work items such as chatty thread replies, celebration posts, and a YouTube link. This confirmed that heuristic mode can reduce obvious non-work noise when mixed channels are included.
Stress validation
A broader run used these channels:
- - INLINECODE47
- INLINECODE48
- INLINECODE49
- INLINECODE50
- INLINECODE51
Stress settings:
- - INLINECODE52
- INLINECODE53
- mode: INLINECODE54
Observed result:
- - total raw: 4580
- filtered keep: 2763
- total requests: 48
- duration: ~85.52s
- ratelimit retries: 0
- failed channels: 0
Interpretation:
- - The channel-by-channel/page-by-page design was stable under a moderately aggressive run.
- This does not guarantee immunity from rate limiting in all workspaces.
- It does show that the design is more robust than one giant search/evaluate call.
Important constraints
- - Do not assume the Slack API is callable directly from host Python with only the xoxc token. In practice the browser session context matters; call from the page context first.
- Do not try one giant all-history query first; Slack rate-limits and long browser evaluations will time out.
- Do not claim the export is complete if channel-level calls failed. Report partial coverage clearly.
- Treat the xoxc token and any cookies as sensitive. Do not echo them back to the user or store them in exported files.
- Do not silently widen "work-related" to mean everything. If the user wants "all work-related threads," state clearly that this is a heuristic unless they also provide a channel whitelist, date range, or keyword rule.
- Do not present heuristic filtering as moderation-, compliance-, HR-, or legal-grade classification.
Publish-ready status
At this stage, the skill is suitable for beta/publication-candidate use when these caveats are respected:
- - Requires an attached, logged-in Slack web tab via Browser Relay
- Relies on Slack web client behavior that may change over time
- Uses heuristic filtering for "work-like" exports unless
strict or raw mode is chosen - Is strongest for operator-led exports, not unattended compliance archiving
If publishing publicly, describe it as an advanced/browser-relay Slack export skill rather than a universal Slack export solution.
Failure semantics
Handle failures explicitly:
- Meaning: wrong tab, logged-out tab, or a host-side request that lacks the browser session context.
- Fix: confirm the attached Slack tab is logged in and re-run from page context.
- Meaning: too many requests too quickly or query scope too broad.
- Fix: sleep and retry the same page; reduce channel count; add
after: date filters.
- - browser evaluate timeout
- Meaning: too much work packed into one evaluate call.
- Fix: split by channel and page; keep each evaluate short.
- Meaning: some channels completed and some failed.
- Fix: save raw progress, report completed channels, and retry only failed channels.
Recommended query patterns
Use these building blocks:
- - User filter: INLINECODE60
- Thread-only: INLINECODE61
- Channel filter: INLINECODE62
- Time filter if needed: INLINECODE63
Typical query:
CODEBLOCK0
Preferred output columns
Use these columns unless the user asks otherwise:
- - INLINECODE64
- INLINECODE65
- INLINECODE66
- INLINECODE67
- INLINECODE68
- INLINECODE69
- INLINECODE70
- INLINECODE71
Scripts
- -
scripts/export_slack_threads.py — channel-by-channel Slack thread exporter driven through the attached Chrome relay tab. - INLINECODE73 — rerun export using the
failed-channels-out file from a previous run.
Run scripts with explicit parameters instead of editing code in place.
Example:
CODEBLOCK1
If coverage is too broad
Narrow the export before retrying:
- - Use a shorter date range.
- Use channel whitelist first.
- Export raw first, then run a second-pass filter for “work-like” heuristics.
- Sample channel distribution first, then choose high-signal channels before doing the full export.
Retry workflow
When a run is partial:
- 1. Keep the raw JSONL from the first run.
- Save failed channels with
--failed-channels-out. - Retry only those channels using
scripts/retry_failed_channels.py. - If needed, pass
--resume-from-jsonl so the retry output starts from the previous raw capture instead of rebuilding from zero.
Retry example:
CODEBLOCK2
Supported options
The script now supports these operational controls:
- -
--channel-file — read channel names from a file - INLINECODE79 /
--before — date windowing - INLINECODE81 — continue from a previous raw export
- INLINECODE82 — save failed channels for targeted retry
- INLINECODE83 — save structured run summary
- INLINECODE84 — sample page-1 volume per channel before full export
- INLINECODE85
-
strict: export only the requested channel/date slice, no work-like heuristic filtering
-
raw: keep everything collected after the query constraints; useful for audit/review datasets
-
heuristic: apply work-related channel/text filtering after collection
- -
--keyword / --channel-hint — extend heuristic filtering for a specific workspace
Filtering guidance
For “work-related” exports, prefer this order:
- 1. Channel whitelist / channel-name hints
- Explicit date range
- Keyword heuristic on message text
- Manual review for edge channels
If the user says "all work-related threads", explain that this is heuristic unless they provide a strict channel whitelist or date range.
Mode selection guide
Use strict when:
- - the user already knows which channels matter
- the goal is faithful export within explicit channel/date constraints
- the user does not want extra classifier behavior
Use raw when:
- - the user wants a review dataset first
- a human or later pipeline will do the filtering
- auditability matters more than noise reduction
Use heuristic when:
- - the user asks for "work-related" rather than a strict channel list
- there are mixed-signal channels in scope
- reducing obvious non-work chatter is valuable
If unsure, start with strict or raw, then derive a second-pass heuristic export for comparison.
Slack 线程导出
当需要从真实已登录浏览器标签页(而非机器人令牌或管理 API)导出 Slack 数据时,使用此技能。
范围与非目标
此技能特意针对通过已连接的 Chrome 浏览器中继标签页进行已登录 Slack Web 导出进行了优化。
它擅长:
- - 跨选定频道导出单个用户的线程回复
- 从 Slack 搜索构建 CSV/JSONL 存档
- 按频道/日期缩小结果范围,并可选择减少非工作噪音
- 通过可恢复的原始输出和失败频道重试来应对部分失败
它不旨在:
- - 作为通用的 Slack 管理/导出替代方案
- 作为基于机器人令牌或 SCIM 的导出工具
- 作为保证完整的法律/合规存档
- 作为与工作相关消息的通用分类器
工作流程
- 1. 确保用户已通过浏览器中继连接 Slack 标签页,并使用 profile=chrome。
- 使用浏览器工具或 openclaw browser --browser-profile chrome 确认已连接的标签页是正确的 Slack 工作区。
- 从 Slack 标签页读取 localStorage.localConfig_v2,以获取 Web 客户端使用的活跃团队元数据和 xoxc 令牌。
- 在页面上下文内部使用 fetch(/api/search.messages, ...) 查询 Slack 的内部 search.messages 端点,以便已登录的浏览器会话提供正确的 cookie/会话上下文。
- 以小批量导出:
- 优先按频道逐个查询,而非一次大型查询。
- 优先逐页循环(count=100,递增 page)。
- 遇到 ratelimited 时通过休眠/重试进行退避。
- 当 count == 0 或 < 100 时停止。
- 6. 在写入文件前排除不需要的记录:
- 丢弃 IM/MPIM 结果。
- 当用户需要线程回复时,仅保留其永久链接包含 thread_ts= 的记录。
- 应用用户要求的业务过滤器(频道白名单、关键词过滤器、日期范围等)。
- 7. 保存两种文件:
- 用于审计/调试的原始 JSONL
- 用于用户实际交付物的清洗后 CSV
工作原理
此技能不主要依赖抓取可见的 Slack DOM 行。相反,它利用 Slack Web 使用的相同内部搜索流程:
- 1. 连接到用户已登录的 Slack 浏览器标签页。
- 从 localStorage.localConfig_v2 读取活跃工作区元数据。
- 仅在页面上下文内部重用 Web 客户端的 xoxc 令牌。
- 从已连接的 Slack 页面调用 fetch(/api/search.messages, ...),使请求继承浏览器会话 cookie 和客户端上下文。
- 逐页浏览 Slack 搜索结果并将其转换为可导出的行。
这比主机端请求更可靠,因为 Slack 搜索通常同时需要令牌和实时登录的浏览器会话。
操作数字与限制
除非有明确理由不这样做,否则使用以下默认值:
- - 每个 search.messages 请求使用 count=100
- 这是此处 Web 客户端路径使用的实际最大批次大小。
- - 正常页面请求之间使用 sleep-seconds=0.5
- 遇到 ratelimited 时,在重试同一页面之前至少休眠 3-5 秒
- 每个频道以 max-pages=60 作为实际上限
- 优先一次处理 1 个频道或短白名单,而非扫描整个工作区
实用指南:
- - 通常安全: 1 个用户 + 1 个频道 + 1-20 页
- 仍然合理: 1 个用户 + 3-10 个频道 + 带 0.5 秒延迟的分页
- 有风险: 跨整个工作区的一次性全历史查询
- 非常危险: 一次大型页面上下文评估,在一次调用中循环处理多个频道和多个页面
为何重要:
- - 如果请求过快或查询范围过广,Slack 将返回 ratelimited。
- 如果一次评估调用中打包了太多工作,浏览器评估可能会超时。
- 最安全的形式是多次短调用,而非一次大型调用。
实际验证内容
这些并非仅理论上的说法;该工作流程已针对真实已登录的 Slack 标签页进行了实践验证。
小范围验证
测试形式:
- - 频道:tech-team、moonlight、dev-part
- 范围:after=2026-03-01、before=2026-03-15
- 模式:strict
观察结果:
- - tech-team:9 行
- moonlight:29 行
- dev-part:0 行
- 原始总数:38
- 唯一原始数:38
- 失败频道:0
- 总请求数:3
- 持续时间:~5.05 秒
这验证了:
- - --before
- --after
- --channel-file
- --summary-json
- --failed-channels-out
- --resume-from-jsonl
- --preflight
- strict 模式
启发式过滤验证
为证明 heuristic 模式比 strict 模式做得更多,第二次测试包含了边缘频道:
- - tech-team
- moonlight
- random
- music
- ai서비스활용-lounge
观察结果:
- - strict:保留 50 行
- heuristic:保留 40 行
- 被启发式过滤移除:10 行
- random:9
- music:1
被移除的示例是休闲/非工作项目,如闲聊式线程回复、庆祝帖子和一个 YouTube 链接。这证实了当包含混合频道时,启发式模式可以减少明显的非工作噪音。
压力验证
一次更广泛的运行使用了以下频道:
- - moonlight
- dev-part
- tech-team
- bob
- cms
压力设置:
- - max-pages=20
- sleep-seconds=0.05
- 模式:heuristic
观察结果:
- - 原始总数:4580
- 过滤后保留:2763
- 总请求数:48
- 持续时间:~85.52 秒
- 速率限制重试:0
- 失败频道:0
解读:
- - 逐频道/逐页设计在适度激进的运行下保持稳定。
- 这不保证在所有工作区中免于速率限制。
- 但这确实表明该设计比一次大型搜索/评估调用更稳健。
重要约束
- - 不要假设仅凭 xoxc 令牌即可从主机 Python 直接调用 Slack API。实际上浏览器会话上下文很重要;首先从页面上下文调用。
- 不要首先尝试一次大型全历史查询;Slack 速率限制和长时间浏览器评估会超时。
- 如果频道级调用失败,不要声称导出是完整的。清晰报告部分覆盖情况。
- 将 xoxc 令牌和任何 cookie 视为敏感信息。不要将其回显给用户或存储在导出的文件中。
- 不要在未明确说明的情况下将与工作相关悄然扩大为包含所有内容。如果用户想要所有与工作相关的线程,请明确说明这是启发式的,除非他们还提供了频道白名单、日期范围或关键词规则。
- 不要将启发式过滤呈现为审核、合规、人力资源或法律级别的分类。
发布就绪状态
在此阶段,当尊重以下注意事项时,该技能适用于测试版/发布候选用途:
- - 需要通过浏览器中继连接已登录的 Slack Web 标签页
- 依赖可能随时间变化的 Slack Web 客户端行为
- 除非选择 strict 或 raw 模式,否则对类似工作的导出使用启发式过滤
- 最适合操作员主导的导出,而非无人值守的合规存档
如果公开发布,请将其描述为高级/浏览器中继 Slack 导出技能,而非通用 Slack 导出解决方案。
失败语义
明确处理失败:
- 含义:错误的标签页、已注销的标签页或缺少浏览器会话上下文的主机端请求。
- 修复:确认已连接的 Slack 标签页已登录,并从页面上下文重新运行。
- 含义:请求过快或查询范围过广。
- 修复:休眠并重试同一页面;减少频道数量;添加 after: 日期过滤器。
- 含义:一次评估调用中打包了太多工作。
- 修复:按频道和页面拆分;保持每次评估简短。
- 含义:部分频道完成,部分失败。
- 修复:保存原始进度,报告已完成的频道,仅重试失败的频道。
推荐