Skip to content

feat: forward identity fields from all bots to control plane#552

Merged
ColeMurray merged 2 commits intomainfrom
phase-4/identity-forwarding
Apr 24, 2026
Merged

feat: forward identity fields from all bots to control plane#552
ColeMurray merged 2 commits intomainfrom
phase-4/identity-forwarding

Conversation

@ColeMurray
Copy link
Copy Markdown
Owner

@ColeMurray ColeMurray commented Apr 24, 2026

Summary

Phase 4 of the unified user model migration (#523). Wires all three bots to send identity fields when creating sessions, enabling the control plane (deployed in Phase 2, PR #550) to resolve canonical user IDs via resolveOrCreateUser.

  • GitHub bot: forwards scmUserId: String(sender.id) alongside existing scmLogin in all 4 handlers (review requested, PR opened, issue comment, review comment)
  • Slack bot: calls getUserInfo to resolve display name + email, forwards actorUserId, actorDisplayName, actorEmail to session creation
  • Linear bot: calls fetchUser (added in pre-step 7) to resolve display name + email, forwards actorUserId, actorDisplayName, actorEmail to session creation
  • Web route: already sends scmUserId: user.id — no changes needed (Phase 2 wired this)

All identity resolution is best-effort: getUserInfo/fetchUser failures are swallowed, and the session is created with whatever fields are available. The control plane's resolveProviderIdentity handles missing fields by returning null (session gets user_id = NULL).

Test plan

  • GitHub bot: 100 tests passing — 4 handler tests assert scmUserId matches sender.id
  • Slack bot: 45 tests passing — new test verifies actorUserId, actorDisplayName, actorEmail forwarded from getUserInfo
  • Linear bot: 96 tests passing — fetchUser already tested (4 tests in linear-client.test.ts)
  • Control plane: 994 unit + 325 integration tests passing (unchanged)
  • Web: 185 tests passing (unchanged)
  • All packages typecheck and lint clean

Summary by CodeRabbit

  • New Features

    • Session creation now captures and forwards richer user identity details (numeric IDs, display names, emails) from GitHub, Linear, and Slack integrations; session requests include these identity fields.
  • Tests

    • Added and extended tests to validate identity fields are included in session creation across handlers and bot integrations.

Wire GitHub bot, Slack bot, and Linear bot to send identity fields
(scmUserId/actorUserId, actorDisplayName, actorEmail) when creating
sessions, enabling the control plane to resolve canonical user IDs.

Part of unified user model Phase 4 (#523).
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 89fbe248-3906-4c86-bce1-e6442bfb6155

📥 Commits

Reviewing files that changed from the base of the PR and between 0dea33a and 566dd6e.

📒 Files selected for processing (2)
  • packages/linear-bot/src/webhook-handler.ts
  • packages/slack-bot/src/index.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/linear-bot/src/webhook-handler.ts
  • packages/slack-bot/src/index.test.ts

📝 Walkthrough

Walkthrough

Session creation flows for GitHub, Linear, and Slack bots now capture and forward actor identity fields (e.g., scmUserId, actorUserId, actorDisplayName, actorEmail) to the control-plane when creating sessions.

Changes

Cohort / File(s) Summary
GitHub Bot Handler
packages/github-bot/src/handlers.ts
Adds scmUserId to createSession call sites; handlers now pass String(sender.id) into session creation payload.
GitHub Bot Tests
packages/github-bot/test/handlers.test.ts
Updates tests to assert scmUserId is included and equals expected GitHub user ID values.
Linear Bot Handler
packages/linear-bot/src/webhook-handler.ts
Resolves Linear user record for webhook.appUserId, extracts optional name and email, and passes actorUserId, actorDisplayName, actorEmail into createSession.
Slack Bot Handler
packages/slack-bot/src/index.ts
Adds getUserInfo lookup in startSessionAndSendPrompt; forwards actorUserId, actorDisplayName, actorEmail to control-plane /sessions; lookup failures do not block session creation.
Slack Bot Tests
packages/slack-bot/src/index.test.ts
Mocks getUserInfo; adds tests verifying actorUserId, actorDisplayName, actorEmail, and spawnSource: "slack-bot" in the control-plane session POST, and behavior when getUserInfo throws.

Sequence Diagram(s)

sequenceDiagram
  participant Bot as Bot (GitHub/Slack/Linear)
  participant Provider as Provider API (GitHub/Slack/Linear)
  participant Control as Control Plane (/sessions)

  Bot->>Provider: optional user-info lookup (id, name, email)
  Note right of Provider: may return identity or fail
  Bot->>Control: POST /sessions with actorUserId, actorDisplayName?, actorEmail?, spawnSource
  Control-->>Bot: session created (response)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • open-inspect

Poem

🐰 I hopped through code with nimble paws,

Adding names and emails to session laws.
From GitHub, Slack, and Linear bright,
Each actor shines into the light.
Hooray — more context for each bot's plight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: forward identity fields from all bots to control plane' accurately describes the main objective of the changeset: extending all three bots (GitHub, Slack, Linear) to forward identity fields during session creation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase-4/identity-forwarding

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 and usage tips.

@github-actions
Copy link
Copy Markdown

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

Copy link
Copy Markdown
Contributor

@open-inspect open-inspect Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

PR Title and number: feat: forward identity fields from all bots to control plane (#552)
Author: @ColeMurray
Files changed count, additions/deletions: 5 files, +177/-7

This wires the GitHub, Slack, and Linear bot session creation paths to forward provider identity fields to the control plane so canonical users can be resolved there. I reviewed the changed code paths and ran the touched package test suites locally; the implementation looks consistent and the new Slack/GitHub coverage matches the behavior in the diff.

Critical Issues

  • None.

Suggestions

  • [Testing] packages/linear-bot/src/webhook-handler.ts:515 - This change adds new behavior in the Linear webhook path, but the PR does not add a webhook-level assertion that actorUserId, actorDisplayName, and actorEmail reach session creation. The existing fetchUser unit tests cover the helper itself; adding one end-to-end handler assertion would make regressions here easier to catch.

Nitpicks

  • None.

Positive Feedback

  • GitHub bot forwarding is applied consistently across all four handlers, which keeps the identity behavior uniform.
  • Slack bot treats identity enrichment as best-effort and preserves the existing session creation path when profile lookup data is unavailable.
  • The new Slack and GitHub assertions are focused on externally visible behavior rather than implementation details.

Questions

  • None.

Verdict

Approve: Ready to merge, no blocking issues.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
packages/linear-bot/src/webhook-handler.ts (1)

494-523: Consolidate the two if (appUserId) blocks.

Lines 494–501 and 519–523 both guard on appUserId. Merging them keeps related identity/preference resolution together and avoids an easy-to-miss second lookup when the field is optional.

♻️ Suggested refactor
   let userModel: string | undefined;
   let userReasoningEffort: string | undefined;
+  let actorDisplayName: string | undefined;
+  let actorEmail: string | undefined;
   const appUserId = webhook.appUserId;
   if (appUserId) {
     const prefs = await getUserPreferences(env, appUserId);
     if (prefs?.model) {
       userModel = prefs.model;
     }
     userReasoningEffort = prefs?.reasoningEffort;
+
+    try {
+      const linearUser = await fetchUser(client, appUserId);
+      actorDisplayName = linearUser?.name;
+      actorEmail = linearUser?.email ?? undefined;
+    } catch {
+      // best-effort
+    }
   }

   const labelModel = extractModelFromLabels(labels);
   // ... existing resolveSessionModelSettings call ...

-  // ─── Resolve user identity ─────────────────────────────────────────────
-
-  let actorDisplayName: string | undefined;
-  let actorEmail: string | undefined;
-  if (appUserId) {
-    const linearUser = await fetchUser(client, appUserId);
-    actorDisplayName = linearUser?.name;
-    actorEmail = linearUser?.email ?? undefined;
-  }
-
   // ─── Create session ───────────────────────────────────────────────────
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/linear-bot/src/webhook-handler.ts` around lines 494 - 523, Merge the
two separate appUserId guards into a single if (appUserId) block so preference
and identity resolution run together: inside that block call
getUserPreferences(env, appUserId) and assign userModel and userReasoningEffort
from its result, then call fetchUser(client, appUserId) and set actorDisplayName
and actorEmail from the returned linearUser; ensure you preserve use of
extractModelFromLabels/resolveSessionModelSettings after
userModel/userReasoningEffort are set and keep variable names (appUserId,
userModel, userReasoningEffort, actorDisplayName, actorEmail) unchanged.
packages/slack-bot/src/index.test.ts (1)

555-668: Solid integration test for identity forwarding.

Good coverage: exercises the full block_actionsstartSessionAndSendPromptcreateSession path, stubs all required control-plane endpoints, and asserts the exact spawnSource plus actor fields (display_name preference over real_name/name is covered since profile.display_name: "Jane" is asserted).

One optional suggestion: consider adding a negative test that getUserInfo rejection / ok: false still results in session creation (validating the best-effort try/catch in index.ts lines 899–909). That swallow path is currently only covered indirectly via the default { ok: true, user: undefined } mock.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/slack-bot/src/index.test.ts` around lines 555 - 668, Add a negative
test that simulates getUserInfo failing (either
mockGetUserInfo.mockResolvedValue({ ok: false }) and another case
mockRejectedValue(new Error(...))) and assert startSessionAndSendPrompt still
triggers createSession on the control plane; specifically, reuse the existing
test flow (payload, pending KV entry, CONTROL_PLANE.fetch stubs) but verify the
sessionCall exists and that actorDisplayName/actorEmail are absent or undefined
while spawnSource === "slack-bot", to validate the try/catch path in getUserInfo
handling.
packages/slack-bot/src/index.ts (2)

80-91: createSession now takes 10 positional parameters — consider switching to an options object.

With slackUserId, actorDisplayName, and actorEmail appended, the signature is getting hard to read and easy to misuse (several are optional strings of the same type, so a transposed argument would silently compile and run wrong). Switching to a params object also mirrors the pattern already used in packages/github-bot/src/handlers.ts and packages/linear-bot/src/webhook-handler.ts's createSession functions, which aids cross-bot consistency.

♻️ Suggested signature
 async function createSession(
   env: Env,
-  repo: RepoConfig,
-  title: string | undefined,
-  model: string,
-  reasoningEffort: string | undefined,
-  branch: string | undefined,
-  traceId?: string,
-  slackUserId?: string,
-  actorDisplayName?: string,
-  actorEmail?: string
+  params: {
+    repo: RepoConfig;
+    title?: string;
+    model: string;
+    reasoningEffort?: string;
+    branch?: string;
+    slackUserId?: string;
+    actorDisplayName?: string;
+    actorEmail?: string;
+  },
+  traceId?: string
 ): Promise<{ sessionId: string; status: string } | null> {

Call sites would then destructure params — a larger change, so feel free to defer if out of scope for this PR.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/slack-bot/src/index.ts` around lines 80 - 91, The createSession
function signature is overcrowded with 10 positional parameters (env, repo,
title, model, reasoningEffort, branch, traceId, slackUserId, actorDisplayName,
actorEmail); convert it to accept a single params object (e.g.
createSession(env, repo, params)) where params is an object with named
properties for title, model, reasoningEffort, branch, traceId, slackUserId,
actorDisplayName, and actorEmail (mark appropriate fields optional), update the
function implementation to destructure params, and update all call sites to pass
an object (or destructured object) instead of positional args so arguments
cannot be transposed and to match the pattern used by the other bots'
createSession functions.

896-923: Best-effort identity resolution pattern looks correct.

The try/catch correctly swallows failures, and the display_name || real_name || name fallback chain matches Slack's documented preference order. Using || (not ??) is intentional here since Slack's profile.display_name can be an empty string when unset, and the fallback to real_name is the desired behavior in that case.

One minor note: getUserInfo adds an extra round-trip to Slack on every session creation. This runs inside waitUntil (background), so it won't affect the 3s Slack response budget, but if Slack's users.info ever slows down it will extend end-to-end "session started" latency. Consider caching the user profile in KV (e.g., 1-hour TTL keyed on userId) as a follow-up if this becomes a hot path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/slack-bot/src/index.ts` around lines 896 - 923, Best-effort identity
resolution currently calls getUserInfo on every session creation which adds an
extra Slack round-trip; to reduce latency, first try reading a cached profile
from KV (keyed by userId) and only call getUserInfo on a cache miss, then store
the resolved profile (display_name, real_name, name, email) into KV with a
~1-hour TTL; preserve the existing fallback logic for displayName and email and
the try/catch behavior (i.e., if KV read or getUserInfo fails, continue with
undefined displayName/email), and reference getUserInfo, displayName, email, and
createSession so the cache read/write wraps the current logic before
createSession is invoked (inside the waitUntil background flow).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/linear-bot/src/webhook-handler.ts`:
- Around line 494-523: Merge the two separate appUserId guards into a single if
(appUserId) block so preference and identity resolution run together: inside
that block call getUserPreferences(env, appUserId) and assign userModel and
userReasoningEffort from its result, then call fetchUser(client, appUserId) and
set actorDisplayName and actorEmail from the returned linearUser; ensure you
preserve use of extractModelFromLabels/resolveSessionModelSettings after
userModel/userReasoningEffort are set and keep variable names (appUserId,
userModel, userReasoningEffort, actorDisplayName, actorEmail) unchanged.

In `@packages/slack-bot/src/index.test.ts`:
- Around line 555-668: Add a negative test that simulates getUserInfo failing
(either mockGetUserInfo.mockResolvedValue({ ok: false }) and another case
mockRejectedValue(new Error(...))) and assert startSessionAndSendPrompt still
triggers createSession on the control plane; specifically, reuse the existing
test flow (payload, pending KV entry, CONTROL_PLANE.fetch stubs) but verify the
sessionCall exists and that actorDisplayName/actorEmail are absent or undefined
while spawnSource === "slack-bot", to validate the try/catch path in getUserInfo
handling.

In `@packages/slack-bot/src/index.ts`:
- Around line 80-91: The createSession function signature is overcrowded with 10
positional parameters (env, repo, title, model, reasoningEffort, branch,
traceId, slackUserId, actorDisplayName, actorEmail); convert it to accept a
single params object (e.g. createSession(env, repo, params)) where params is an
object with named properties for title, model, reasoningEffort, branch, traceId,
slackUserId, actorDisplayName, and actorEmail (mark appropriate fields
optional), update the function implementation to destructure params, and update
all call sites to pass an object (or destructured object) instead of positional
args so arguments cannot be transposed and to match the pattern used by the
other bots' createSession functions.
- Around line 896-923: Best-effort identity resolution currently calls
getUserInfo on every session creation which adds an extra Slack round-trip; to
reduce latency, first try reading a cached profile from KV (keyed by userId) and
only call getUserInfo on a cache miss, then store the resolved profile
(display_name, real_name, name, email) into KV with a ~1-hour TTL; preserve the
existing fallback logic for displayName and email and the try/catch behavior
(i.e., if KV read or getUserInfo fails, continue with undefined
displayName/email), and reference getUserInfo, displayName, email, and
createSession so the cache read/write wraps the current logic before
createSession is invoked (inside the waitUntil background flow).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fc6375f2-ac1a-4d31-9d10-38cda5fd16ab

📥 Commits

Reviewing files that changed from the base of the PR and between 5a368cc and 0dea33a.

📒 Files selected for processing (5)
  • packages/github-bot/src/handlers.ts
  • packages/github-bot/test/handlers.test.ts
  • packages/linear-bot/src/webhook-handler.ts
  • packages/slack-bot/src/index.test.ts
  • packages/slack-bot/src/index.ts

Address review feedback:
- Merge two `if (appUserId)` blocks in Linear bot handleNewSession
- Add negative test for Slack getUserInfo failure (best-effort path)
@github-actions
Copy link
Copy Markdown

Terraform Validation Results

Step Status
Format
Init
Validate

Note: Terraform plan was skipped because secrets are not configured. This is expected for external contributors. See docs/GETTING_STARTED.md for setup instructions.

Pushed by: @ColeMurray, Action: pull_request

@ColeMurray ColeMurray merged commit e48093a into main Apr 24, 2026
18 checks passed
@ColeMurray ColeMurray deleted the phase-4/identity-forwarding branch April 24, 2026 06:58
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.

1 participant