Skip to content

fix(slack): Enterprise Grid org-wide installs, authorizations[] routing, retry marker (#268) - #277

Merged
patrick-chinchill merged 5 commits into
mainfrom
sync/4.41-sl8b
Oct 1, 2026
Merged

patrick-chinchill merged 5 commits into
mainfrom
sync/4.41-sl8b

Conversation

@patrick-chinchill

@patrick-chinchill patrick-chinchill commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Ports the Enterprise Grid half of #213 (PR B), which is the non-cache part of upstream 907450d7 (vercel/chat#724, chat@4.35.0). The installation-scoped caches already landed in #205.

  • Org-wide OAuth. handle_oauth_callback now keys an org-wide install (is_enterprise_install, team: null) by enterprise.id. If that id is missing it raises missing access_token or enterprise.id. The result gains enterprise_id and is_enterprise_install, and team_id is always the storage key. SlackInstallation gains enterprise_id / is_enterprise_install, which are stored as enterpriseId / isEnterpriseInstall only when set, so a plain install is stored exactly as before.
  • _resolve_event_request_context(payload) returns a RequestContext, "not-applicable" or "unresolved". The HTTP and Socket Mode events_api paths both use it. It prefers authorizations[0] (flag via ??, ids via ||) over the top-level fields. It records team_id, context_team_id and context_channel on RequestContext.
  • Shared resolution on both paths.
    • Slash commands go through _run_slash_command on HTTP and on the socket. On the socket, JSON booleans become "true" / "false".
    • Socket interactive payloads go through _extract_installation_from_interactive_payload.
    • The socket events_api envelope now keeps authorizations, context_team_id, the enterprise fields and is_ext_shared_channel, as upstream does.
  • _with_token_kwargs(**kwargs) is the port of upstream withToken. Under an org-wide context it adds team_id. On calls to the event's channel it adds client_context_team_id. A key the caller set always wins. It is applied at the same 31 call sites where upstream uses withToken. As upstream, chat_stream, schedule/delete-scheduled, files_upload_v2 and oauth_v2_access are not wrapped.
  • Retry marker.
    • _process_event_payload writes slack:event-delivered:{event_id} with a 24 h TTL. The write is fire-and-forget: the task is pinned and failures are logged at debug.
    • _is_duplicate_event_delivery(payload, retry_num) reads the marker only when retry_num > 0. It runs on HTTP (x-slack-retry-num), on the live socket (retry_attempt) and on forwarded socket events (retryNum). If the state read fails, the event is processed.
  • SLACK_USER_ID_EXACT_PATTERN = ^[UW][A-Z0-9]+$.
  • with_bot_token and with_bot_token_async take a keyword-only installation_id=None.

Upstream commits mapped

Tests ported (tests/test_slack_enterprise_grid.py unless noted)

  • handleOAuthCallback: keys org-wide installs by enterprise ID; records the enterprise ID on workspace installs within a Grid org; throws when the org-wide response is missing enterprise.id; org-wide OAuth install round-trips with org-wide event webhooks.
  • socket mode - multi-workspace token resolution: all 5 PR B cases. "drops socket interactive payloads with no installation" was already ported in [4.41/SL0] Slack: installation-scoped caches, drop unresolved installs, strict response_url, bounded regexes #205.
  • withToken enterprise context injection: all 7.
  • event delivery deduplication: all 4.
  • W-prefixed enterprise user IDs: both.
  • event routing via authorizations[]: all 3.
  • withBotToken cache scoping: both, in tests/test_slack_webhook.py::TestInstallationScopedCaches. They use with_bot_token_async, because a coroutine returned through the sync form runs after the context is reset.
  • Python-specific tests:
    • Slack: chat.startStream fails with team_not_found on Enterprise Grid workspaces #95 under an org-wide context: tests/test_slack_api.py::TestStream::test_stream_keeps_the_recipient_team_id_under_an_org_wide_context.
    • Call-site wiring: post_message / fetch_channel_info carry team_id / client_context_team_id.
    • Encrypted round trip of the enterprise fields.
    • Socket interactive org-wide resolution, HTTP slash org-wide resolution, and a "false" string flag.
    • Retry marker: the 24 h TTL write, a state-read failure, a write failure, a malformed retry header, and forwarded retryNum dedupe.
    • Resolution states.
  • Updated existing tests:
    • Socket resolver assertions now expect (id, False).
    • The socket payload-parity test now asserts upstream's 4.41.1 envelope, replacing the old "no is_ext_shared_channel" rule, plus _external_channels population.

Mutation check: disabling the marker, authorizations[0], _with_token_kwargs, the strict flag or the [UW] pattern each makes the matching tests fail.

Fidelity

Upstream Slack test files are not fidelity-mapped, so the target report does not change and scripts/fidelity_target.json is unchanged:

Delta vs committed report (HEAD): missing 100 -> 100 (+0)

(Regenerated after merging origin/main at 5b6bc22: TARGET TOTAL: 964/1064 matched.)

Strict: TOTAL: 733/733 matched (100%), 0 missing, 3 absorbers.

Divergences (recorded in docs/UPSTREAM_SYNC.md, new section "Slack Enterprise Grid…")

Consumer impact

  • Org-wide Grid installs now complete OAuth and route events. They used to raise missing access_token or team.id.
  • A retried event that was already dispatched is now acked and dropped. Every dispatched event costs one extra fire-and-forget state write.
  • Under org-wide installs, Web API calls now carry team_id, and Slack Connect events echo client_context_team_id to their channel.
  • Multi-workspace routing prefers authorizations[0], so Slack Connect events resolve to the receiving installation.
  • Socket Mode shared channels are now marked external.
  • Single-workspace, non-Grid deployments see only the retry marker.

Merge with main (#207 / #208 / #209 part a)

Validation

After merging origin/main (through #201/#297, 5b6bc22): ruff check/format, audit_test_quality.py (0 hard failures), --check-docs, strict fidelity 733/733, pytest 7777 passed / 24 skipped, pyrefly 0 errors.

Merge gate

Independent review findings

  • Fixed (both reviewers, same gap): only 3 of the ~30 _with_token_kwargs call sites had wiring tests, so a dropped wrap would silently lose team_id / client_context_team_id under org-wide installs (upstream's withToken also supplies the token, so a skipped site fails loudly there). test_api_calls_carry_the_resolved_enterprise_context is now parametrized with one case per request-time call site (post/edit text+card, delete, reactions add/remove + reaction-event parent lookup, ephemeral text+card, fetch_messages both directions, fetch_message thread+DM, link-preview fetch, fetch_channel_messages both directions, list_threads, fetch_thread, fetch_channel_info, _lookup_channel, _lookup_user, open_dm, open/update modal, publish_home_view, start_typing, set_assistant_status/title, set_suggested_prompts api_call). Each asserts team_id == "T_GRID_1" and client_context_team_id exactly when the call targets the originating channel. Added test_a_caller_specified_client_context_team_id_is_kept. Test-only, no adapter change.
  • Mutation check: replacing self._with_token_kwargs( with dict( at each of the 31 sites one at a time kills 30/31, and removing the client_context_team_id is None guard is killed. The one survivor is the single-workspace auth_test in initialize (as upstream index.ts:1698). It runs at init time outside any request context, so the wrap is a no-op there and a test would be artificial.
  • Declined: none.

gpt-6-astra: 1 round on 4200922. Verdict: "No actionable regressions found in the diff against the specified merge base."

Bots: CodeRabbit skipped the draft and then hit its rate limit, so it left no review comments. There are no gemini comments.

CI: all checks green on 4200922 (Tests 3.12/3.13, Lint & Type Check, CodeQL).

Closes #268
Part of #184

…ng, retry marker (#268)

Ports the non-cache half of vercel/chat 907450d7 (#724, chat@4.35.0):
- handle_oauth_callback keys org-wide installs by enterprise.id and
  records enterprise_id / is_enterprise_install
- _resolve_event_request_context prefers authorizations[0], shared by
  HTTP and Socket Mode; socket slash/interactive resolve org-wide installs
- _with_token_kwargs injects team_id (org-wide) and
  client_context_team_id (originating channel) at upstream's withToken
  call sites; the #95 chat_stream team_id is unchanged
- slack:event-delivered:{event_id} retry marker (24 h TTL)
- W-prefixed user ids; with_bot_token(installation_id=)

Part of #184.
@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 6 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: cedcb460-0b1c-4403-ab9d-23018386a5c0

📥 Commits

Reviewing files that changed from the base of the PR and between 5b6bc22 and 4200922.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • docs/UPSTREAM_SYNC.md
  • src/chat_sdk/adapters/slack/adapter.py
  • src/chat_sdk/adapters/slack/types.py
  • tests/test_slack_api.py
  • tests/test_slack_enterprise_grid.py
  • tests/test_slack_socket_mode.py
  • tests/test_slack_webhook.py
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Resolve conflicts with #207/#208/#209(a): wrap main's api_call-based
setSuggestedPrompts and the new setStatus loading-messages logic in
_with_token_kwargs (upstream withToken); keep main's renamed #95 mutation
guard; point the live socket retry note at #283 (split from #209).
…te (#268)

Each _with_token_kwargs call site now has a case asserting team_id (and
client_context_team_id on the originating channel) under an org-wide
context; a caller-set client_context_team_id is kept. Mutation-checked:
dropping the wrap at any one of the 30 request-time sites fails a test.
@patrick-chinchill
patrick-chinchill marked this pull request as ready for review October 1, 2026 09:30
@patrick-chinchill
patrick-chinchill marked this pull request as draft October 1, 2026 09:35
@patrick-chinchill
patrick-chinchill marked this pull request as ready for review October 1, 2026 09:35
@patrick-chinchill

Copy link
Copy Markdown
Collaborator Author

Merge gate: CI green (test 3.12, test 3.13, Lint & Type Check, CodeQL / Analyze actions+python); local Codex review (gpt-6-astra, xhigh, --base origin/main) on 4200922: "No actionable regressions found in the diff against the specified merge base. All 1,234 Slack tests passed, and git diff --check reported no issues."; 1 astra round on the final HEAD (origin/main already an ancestor, no further merge needed); bots: CodeRabbit rate-limited (no review comments), no gemini comments. Merging with --admin (Protect Main requires a code-owner approval).

@patrick-chinchill
patrick-chinchill merged commit 90d02de into main Oct 1, 2026
8 checks passed
@patrick-chinchill
patrick-chinchill deleted the sync/4.41-sl8b branch October 1, 2026 09:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[4.41/SL8b] Slack: Enterprise Grid org-wide installs, authorizations[] routing, retry marker (split from #213)

1 participant