Merged
Conversation
…ges and replies The reactions.list Slack API requires a bot token, which fails with user tokens (not_allowed_token_type). Instead, extract reactions from the inline reactions field already present in message and reply payloads. New features: - ReactionEvent: tracks per-message reaction state extracted from inline data, keyed by (channel_id, message_ts) - RelevantThreadEvent: tracks threads where the authenticated user replied or was mentioned, stored in relevant_threads/ stream - Reaction scan: re-fetches last 100 messages per channel and re-checks recent relevant threads for reaction changes, since reactions can be added without changing latest_reply - --reaction-lookback N flag (default 10): controls how many recent relevant threads to re-check for reactions Removed: - reactions.list API call (fetch_user_reactions) - ReactionItemEvent data type - derive_reaction_item_key function Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add test_run_export_reaction_lookback_rechecks_relevant_threads which exercises the reaction_lookback > 0 code path: verifies that relevant threads with unchanged latest_reply are re-fetched and reactions are extracted. Also remove stale references to reactions.list from field descriptions, README comments, and leftover test fixture entries. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…fied Previously --channels defaulted to just "general". Now when --channels is omitted, the exporter processes all channels from the fetched channel list (already filtered to member channels by default). Passing --channels explicitly still restricts to the named channels. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All slack exporter events now use source="slack" instead of per-data-type sources (messages, replies, channels, etc.), and event types use the _created suffix (e.g. message_created, reply_created) instead of _fetched/_extracted/_detected. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Event types are now just the entity name (e.g. "message", "reply", "channel") rather than "message_created", "reply_created", etc. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add test_run_export_all_channels_when_channels_is_none to verify the default behavior of exporting all member channels. Also fix README backfill description to say "oldest date already searched from" (not "oldest message"), and add missing reaction_lookback to the refresh test settings. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wrap the API caller with with_rate_limit_retry() which catches "ratelimited" SlackApiError responses and retries with exponential backoff (starting at 2s, doubling up to 60s, max 7 retries for ~3 minutes total). The retry wraps all Slack API calls since it is applied at the caller level in main.py. The sleep function is injected as a parameter to enable testing without real delays or unittest.mock. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…list The conversations.list API does not reliably include the last_read field across all channel types, resulting in most unread markers being missed. Now fetches last_read per channel via conversations.info, which reliably returns it for channels the user has joined. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ons.info Use the latest message timestamp from conversations.info (already fetched for unread markers) to determine which channels have new messages. Channels where latest hasn't changed since the last export skip the conversations.history forward fetch entirely. The conversations.info calls are no longer cached with the channel list since we need fresh latest timestamps on every run. Channel metadata (names, IDs, membership) is still cached with TTL. The reaction scan still runs for all channels (via the last 100 messages fetch) and doubles as the message source for reply/thread detection when the forward fetch was skipped. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… function Replace with_rate_limit_retry (which returned a closure) with retry_on_rate_limit (a module-level function that takes the api_caller, sleep_fn, method, and params directly). This eliminates the inline function ratchet bump (back to 1). The time.sleep ratchet bump (0->1) remains because the regex catches all forms of importing sleep from the time module. This is a legitimate use of time.sleep for rate limit backoff -- the ratchet description says to "poll for the condition" instead, but rate limit backoff is not a polling situation; we genuinely need to wait before retrying. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated PR created by Claude Code session.