Repository navigation
Conversation
[Medium impact] The changes affect Grid member boot and live events across connected workspace sessions. The remaining helper-documentation requirement must be satisfied before merging. FindingsSummaryThis PR lets Enterprise Grid member workspaces connect when Slack rejects
|
|
🤖 Riley Reviewer (AI review for @gammons). Nothing here was run locally; checks come from CI, Greptile and reading the code. Welcome, @mstsirkin, and thanks. This is a careful first PR. The method table in the description is very useful, and the fallback is tightly scoped: it only runs when userBoot has already failed and auth.test says the session is a grid member workspace. Off a grid, nothing changes, and a test pins that. The comment on
Findings (at 2ff263a) Fix before merge:
Optional:
I couldn't verify one thing by reading: that the org-level session's auth.test always returns Verdict: needs minor changes. Adding the AGENTS.md row (finding 1) unblocks it. Flagged for @gammons: the |
A Grid signs its members in twice: once at the org level, whose session root-state.json lists and stock slk adds as a single workspace, and once per member workspace, whose tokens the desktop app keeps in localConfig_v2. Adding those member workspaces by team id previously produced a workspace that could never connect: the grid rejects client.userBoot for member tokens outright (internal_error), and that failure is fatal to bootstrap. Fall back to a boot result built from what the grid does serve member tokens: the team record, which auth.test already returned, and the mute prefs via users.prefs.get. The conversation lists stay empty because connectWorkspace builds the sidebar from users.conversations on every workspace, and an unread-count failure is already non-fatal there, so a member workspace boots with live WebSocket events and no boot-time badge snapshot. Off a grid nothing changes: a userBoot failure still fails the workspace. The gate is IsGridMember - auth.test returned an enterprise_id that differs from the team id - rather than the error string, matching how a users.conversations failure is already treated on the org workspace (issue gammons#5): keeping the session beats failing it. Testing: grid-member boots synthesize the team record and mutes and skip userBoot; a prefs failure costs only the mute set; non-grid workspaces keep failing. Verified against a live Grid with two member workspaces: both boot with their full sidebars (134 and 103 conversations) and live websockets. Co-Authored-By: Claude Code <noreply@anthropic.com>
2ff263a to
125aa17
Compare
|
Thanks for the thorough review - all findings addressed and force-pushed as a single updated commit (125aa17):
Build, vet, gofmt and the full test suite pass locally. |
|
🤖 Riley Reviewer (AI review for @gammons). Nothing here was run locally; checks come from CI, Greptile and reading the code. Thanks, @mstsirkin, for working through every point so quickly. I compared 125aa17 against 2ff263a: the rest of the change is the same, and all five follow-ups landed the way you described. The new cancellation test is a good one. Without the
Findings (at 125aa17) None. Earlier findings 1–4 are fixed ( Verdict: ready to merge. Nothing blocks it on my side. Flagged for @gammons: the |
|
Follow-up commit f7e0210, because the feature in the first commit has a cost the live grid made obvious: the grid delivers every event to each of the user's connected sessions, so with the org workspace plus member workspaces connected, each message, invalidation and presence change ran the full handler pipeline once per socket (two to three times). On a busy grid that is a constant stream of redundant work - an idle slk measured 6-8% CPU on triple delivery, 0.2% after. Sessions of one grid now share a byte-keyed frame dedupe (short TTL, distinct bytes always processed, hello/reconnect_url/user_typing exempt), with the active workspace's socket never gated since only its handler renders messages. Off a grid nothing changes - the gate is installed only when auth.test reported an enterprise id. Details and measurements are in the commit message. |
|
🤖 Riley Reviewer (AI review for @gammons). Nothing here was run locally; checks come from CI, Greptile and reading the code. Thanks, @mstsirkin, for the follow-up and for measuring it. Running every event two or three times on a grid is real waste, and the commit message explains the design and its trade-offs well. Some parts are well judged: the dedupe key is the frame's exact bytes, so a frame that differs in any way always passes; the active socket is never gated; per-connection frames are exempt; and off a grid nothing changes.
Findings (at f7e0210) Fix before merge:
Optional:
Verdict: changes requested. Finding 1 unblocks it: per-workspace state has to keep updating on every grid session. Flagged for @gammons: the |
f7e0210 to
4c09480
Compare
|
All findings from the dedup review addressed in the amended commit (4c09480): Finding 1 (per-workspace state) - the exemption is now a denylist, as suggested, and it errs the safe way: a frame type not listed is at worst processed redundantly (the pre-dedupe behaviour), while a type wrongly treated as shared would silently go stale. Never deduped: Finding 2 (same-socket repeat) - entries now record the socket that marked them, and only a copy from a different socket drops, so the away-active-away flip lands on its final value. Finding 3 (AGENTS.md) - Tests: |
|
🤖 Riley Reviewer (AI review for @gammons). Nothing here was run locally; checks come from CI, Greptile and reading the code. Thanks, @mstsirkin, for working through the last round so carefully. Turning the dedupe into a denylist is the right way round, and the comment on
Findings (at 4c09480) Fix before merge:
Optional:
Verdict: changes requested. Finding 1 unblocks it: every event type whose handler writes |
4c09480 to
03bd79f
Compare
|
Both findings fixed, plus your optional #3, in the amended commit (03bd79f): Finding 1 - all eight types added ( Finding 2 - Finding 3 (optional, taken) - Full suite plus |
|
🤖 Riley Reviewer (AI review for @gammons). Nothing here was run locally; checks come from CI, Greptile and reading the code. Thanks, @mstsirkin. This push fixes both blockers from last round. The denylist now covers every type the dispatcher maps to a per-workspace handler: I checked each
Findings (at 03bd79f) Fix before merge:
Optional:
Verdict: changes requested. Findings 1 and 2 unblock it; both are small. Flagged for @gammons: the |
03bd79f to
2c1feaa
Compare
|
Both doc findings in the amended commit: the |
6834252 to
af982a9
Compare
|
🤖 Riley Reviewer (AI review for @gammons). Nothing here was run locally; checks come from CI, Greptile and reading the code. Thanks @mstsirkin. af982a9 takes both optional findings from last time. The
Findings Fix before merge:
Optional:
Verdict: needs minor changes. Make message-driven discovery reach every grid sibling, with a test, and this is ready. Flagged for @gammons: over 300 changed lines, a |
|
Finding 1 fixed with option (b) in the amended commit, keeping the dedupe: Tests: |
af982a9 to
2af1b98
Compare
|
🤖 Riley Reviewer (AI review for @gammons). Nothing here was run locally; checks come from CI, Greptile and reading the code. Thanks @mstsirkin. This fixes last pass's blocker the way I'd hoped (option (b)), and splitting the PR into two commits makes it easier to read.
Findings No blockers. Optional:
Verdict: ready to merge. The optional findings above are worth a look but don't block. Flagged for @gammons: over 300 changed lines, a |
|
Both optional findings taken in the amended commit: Finding 1 - the hook now consults Finding 2 - The truncated-reaction gap stays as documented. Full suite plus -race pass. |
2af1b98 to
f1c6c14
Compare
| // grid event gate's discovery hook) use this predicate so the two | ||
| // cannot drift silently - when a subtype is added here, it moves there | ||
| // too. | ||
| func MessageFrameDrivesOnMessage(subtype string, frameEphemeral, embeddedEphemeral bool) bool { |
There was a problem hiding this comment.
MessageFrameDrivesOnMessage adds a reusable exported helper, but the shared-code tables do not list it. AGENTS.md requires adding reusable helpers to those tables in the same commit. Add its purpose and when callers should use it before merging. SetWSEventGate and discoverAndPublish are also missing.
Context Used: AGENTS.md (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
🤖 Riley Reviewer (AI review for @gammons). Nothing here was run locally; checks come from CI, Greptile and reading the code. Thanks @mstsirkin, you took both optionals from the last pass. The discovery hook now only fires for frames
Findings Fix before merge:
Optional:
Verdict: needs minor changes. Making the dispatcher use the predicate, or adding the lockstep test, unblocks it. Flagged for @gammons: |
|
Finding 1 fixed both ways in the amended commit: The gap: The lockstep guard: Finding 2 (optional, taken): Full suite plus -race pass. |
f1c6c14 to
3a5ddb4
Compare
|
🤖 Riley Reviewer (AI review for @gammons). Nothing here was run locally; checks come from CI, Greptile and reading the code. Thanks @mstsirkin. Last pass's blocker is fixed.
Findings Fix before merge:
Optional:
Verdict: needs minor changes. Moving the two AGENTS.md rows into the table unblocks it. Flagged for @gammons: |
The grid delivers every event to each of the user's connected sessions, so a user with the org workspace and member workspaces open - the configuration the previous commit enables - receives each message, invalidation and presence change once per socket, and slk runs the full handler pipeline (SQLite upserts, read-state writes, UI dispatches) two or three times for every event. On a busy grid that is a constant stream of redundant work: measured against a live grid, an idle slk dropped from 6-8% CPU on triple delivery to 0.2% with each event processed once. Sessions of one grid now share a frame dedupe keyed by the frame's bytes with a short TTL: a frame a sibling session already delivered is dropped. Byte identity is the key on purpose - a frame that differs in any byte is a distinct event and is processed, so nothing new is ever dropped. The active workspace's socket is never gated, because only the active workspace's handler renders messages: its delivery passes unconditionally and records the frame, so at worst one inactive sibling that won the delivery race still runs the durable writes alongside it. hello, reconnect_url and user_typing are exempt: they are per-connection, or repeat byte-identically while someone keeps typing. Off a grid nothing changes: the gate is installed only on sessions whose auth.test reported an enterprise id. Testing: dedupe unit tests (TTL expiry, exempt types, distinct bytes, the active bypass and the inactive-first ordering), a StartWebSocket test driving the gate on a live socket, and the full suite plus -race on cmd/slk and internal/slack. Verified against a live grid with the org workspace and two member workspaces connected: idle CPU 0.2%, each message handled at most twice (the active workspace plus a racing sibling) instead of three times, live rendering unaffected. Co-Authored-By: Claude Code <noreply@anthropic.com>
|
Both findings in the amended commit: Finding 1 - the two rows moved into the Text and rendering table, next to Finding 2 (optional, taken) - the test now checks Full suite passes. |
3a5ddb4 to
780b380
Compare
|
🤖 Riley Reviewer (AI review for @gammons). Nothing here was run locally; checks come from CI, Greptile and reading the code. Thanks @mstsirkin. Both points from last pass are fixed. The two AGENTS.md rows for
Findings None blocking, and nothing new. Verdict: ready to merge. Flagged for @gammons: |
Summary
An Enterprise Grid signs its members in twice: once at the org level - the session
root-state.jsonlists, so stock slk offers the grid as a single workspace - and once per member workspace, whose per-workspace tokens the desktop app keeps inlocalConfig_v2. slk's--add-workspacedesktop flow reads that store by team id, so a member workspace can be configured, but until now it could never connect: the grid rejectsclient.userBootfor member tokens outright, and that failure is fatal tobootstrap.Run.This PR makes a member workspace boot from what the grid does serve its token, so a Grid user can add internal/external/member workspaces the way the desktop app presents them.
Background: the grid serves the two session classes different methods
Observed against a live Grid (one org-level workspace, two member workspaces):
client.userBoot/client.channelsinternal_errorclient.counts/conversations.view/users.channelSections.list/client.shouldReloadteam_is_restrictedusers.conversations/conversations.history/users.prefs.get/dnd.info/users.listenterprise_is_restrictedagent_versionset)So neither token can run the full boot sequence, but between them everything is covered. The org-level session is what slk uses today (it is what
root-state.jsonoffers); this PR is about the member tokens.What changes
Clientrecordsenterprise_idfrom auth.test;IsGridMember()reports a session whose enterprise id differs from its team id.bootAdapter.UserBootfalls back to a synthesized boot result when userBoot fails and the session is a grid member:hydrateFirstSightneeds for the workspace's parent cache row.users.prefs.get, which the grid does serve member tokens.connectWorkspacebuilds the sidebar fromusers.conversationson every workspace (userBoot is only that call's fallback source), and an emptyIsOpenmakesdropClosedMPIMsa no-op.IsGridMemberrather than the error string, matching how ausers.conversationsfailure is already treated on the org workspace (Enterprise App Token Support #5): keeping the session beats failing it.Known costs on a member workspace
client.countsisteam_is_restrictedfor member tokens.bootstrap.Runalready treats a counts failure as non-fatal, and WebSocket events keep the read state current, so only the boot-time badges are lost.conversations.viewis rejected, so the open-channel path lands on its existingconversations.historyfallback.invalid_auth), which is the pre-existing behavior the org workspace already logs and retries.Testing
users.prefs.getfailure costs only the mute set; non-grid workspaces keep failing on userBoot;IsGridMemberacross org-level / member / non-grid / unconnected clients.go build ./...,go vet ./...,gofmt -l .clean;go test ./...passes.🤖 Generated with Claude Code