Skip to content

feat(work-apps): add Slack Work App integration#1832

Closed
victor-inkeep wants to merge 29 commits intomainfrom
feat/inkeep-slack-app-v5
Closed

feat(work-apps): add Slack Work App integration#1832
victor-inkeep wants to merge 29 commits intomainfrom
feat/inkeep-slack-app-v5

Conversation

@victor-inkeep
Copy link
Contributor

@victor-inkeep victor-inkeep commented Feb 9, 2026

Summary

Adds Slack as the first real-time Work App integration — workspace installation, user account linking, slash commands, @mentions, and a manage UI dashboard.

Architecture & Flow

1. Workspace Installation (OAuth via Nango)

Dashboard install button → Nango OAuth session → Slack OAuth consent → callback stores workspace in DB + Nango

  • agents-manage-ui/src/features/work-apps/slack/components/workspace-hero.tsx — install UI
  • packages/agents-work-apps/src/slack/routes/oauth.ts — OAuth redirect + callback handler
  • packages/agents-work-apps/src/slack/services/nango.ts — Nango token management, workspace connection cache (60s TTL), tenant-agnostic teamId lookup
  • packages/agents-core/src/data-access/runtime/workAppSlack.ts — DB operations for workspaces, user mappings, channel configs (atomic upsert via onConflictDoUpdate)

2. User Account Linking

/inkeep link in Slack → generates JWT link token → user visits dashboard link page → verifies token (prefers session userId) → creates user mapping

  • packages/agents-core/src/utils/slack-link-token.ts — JWT link token sign/verify
  • packages/agents-work-apps/src/slack/routes/users.ts — link verification (uses session userId over body), user management endpoints
  • agents-manage-ui/src/app/link/page.tsx — dashboard link page

3. Agent Configuration (Dashboard)

Admin sets workspace default agent and per-channel defaults via the manage UI

  • agents-manage-ui/src/features/work-apps/slack/components/agent-configuration-card.tsx — workspace + channel default config UI
  • agents-manage-ui/src/features/work-apps/slack/components/slack-dashboard.tsx — main Slack dashboard page
  • packages/agents-work-apps/src/slack/routes/workspaces.ts — workspace settings, channel config CRUD, health check, user listing (13 OpenAPI endpoints)
  • packages/agents-work-apps/src/slack/services/agent-resolution.ts — resolves effective agent (channel default > workspace default)

4. Public Responses (@Inkeep <message>)

@mention in Slack → resolve agent + verify user link (parallel) → stream response via Slack chatStream API

  • packages/agents-work-apps/src/slack/services/events/app-mention.ts — @mention handler with parallelized lookups, usage hint for empty mentions
  • packages/agents-work-apps/src/slack/services/events/streaming.ts — SSE streaming to Slack with incremental updates
  • packages/agents-work-apps/src/slack/services/events/utils.ts — thread context, error classification, bounded user mapping cache (5000 max, 5 min TTL), markdown-to-mrkdwn

5. Private Responses (/inkeep)

/inkeep → opens modal (project + agent + prompt) → ephemeral response with Follow Up button → multi-turn conversation via conversationId

  • packages/agents-work-apps/src/slack/services/commands/index.ts — slash command dispatcher (link, status, run, list, help, agent picker)
  • packages/agents-work-apps/src/slack/services/modals.ts — modal builders (agent selector, follow-up, message shortcut)
  • packages/agents-work-apps/src/slack/services/events/modal-submission.ts — modal submission + follow-up handler (always ephemeral, 30s fetch timeout)
  • packages/agents-work-apps/src/slack/services/events/block-actions.ts — button click handlers (follow-up modal, agent selector)
  • packages/agents-work-apps/src/slack/services/blocks/index.ts — Block Kit builders (conversation layout, context blocks, follow-up button, help message)

6. API Wiring & Auth

  • agents-api/src/domains/work-apps/index.ts — mounts /work-apps/slack/* routes
  • agents-api/src/createApp.ts — registers work-apps routes with CORS, auth context set before sessionContext, shared workAppsAuth middleware
  • agents-api/src/middleware/cors.tsworkAppsCorsConfig for dashboard cross-origin requests
  • agents-api/src/middleware/manageAuth.ts + runAuth.ts — Slack user JWT authentication (early return on recognized-but-invalid tokens)
  • packages/agents-core/src/utils/slack-user-token.ts — JWT token for Slack-to-API delegation
  • packages/agents-core/src/utils/sse-parser.ts — shared SSE response parser (consolidated from EvaluationService)
  • packages/agents-work-apps/src/slack/middleware/permissions.ts — workspace admin + channel member authorization with tenantRole resolution via org membership lookup
  • packages/agents-work-apps/src/slack/routes/events.ts — Nango webhook signature verification

7. DB Schema

  • packages/agents-core/drizzle/runtime/0012_add-slack-work-app-tables.sqlwork_app_slack_workspaces, work_app_slack_user_mappings, work_app_slack_channel_agent_configs
  • packages/agents-core/src/db/runtime/runtime-schema.ts — Drizzle schema definitions

8. Supporting Files

  • packages/agents-work-apps/src/slack/i18n/strings.ts — centralized Slack-facing UI strings
  • packages/agents-work-apps/src/slack/services/security.ts — HMAC signature verification
  • packages/agents-work-apps/src/slack/services/client.ts — Slack Web API client wrapper
  • packages/agents-work-apps/src/slack/slack-app-manifest.json — example Slack app manifest with <YOUR_API_DOMAIN> placeholders

9. Documentation

Customer-facing docs under Talk to Your Agents → Slack at agents-docs/content/talk-to-your-agents/slack/

  • overview.mdx — what the Slack app does, key features, architecture flow diagram, getting started cards
  • installation.mdx — step-by-step OAuth installation from dashboard, account linking via /inkeep link, uninstall
  • commands.mdx — full @Inkeep mention and /inkeep slash command reference, multi-turn follow-up, agent resolution, troubleshooting
  • configuration.mdx — workspace default agent, channel defaults, linked users management, health checks, permission model

Test plan

  • Install Slack app via dashboard, verify workspace appears
  • Link account via /inkeep link, verify on dashboard
  • @Inkeep <message> — public streaming response in thread
  • @Inkeep in thread — auto-executes with thread context
  • @Inkeep with no message — shows usage hint
  • /inkeep <message> — private ephemeral response
  • /inkeep — modal with agent picker, Follow Up button works for multi-turn
  • /inkeep help — displays public/private usage guide
  • /inkeep status — shows linked account + active agent
  • Set workspace default agent in dashboard
  • Set channel default agent, verify it takes priority
  • Uninstall workspace, verify cleanup

Backend (agents-work-apps):
- Slack event handlers: slash commands, @mentions, message shortcuts
- Agent resolution service (channel > workspace priority)
- Streaming responses with SSE to Slack
- Block Kit UI builders for modals, messages, and buttons
- Centralized i18n for all Slack UI text

Authentication & Security:
- JWT tokens: SlackUserToken (agent calls), SlackLinkToken (account linking)
- HMAC-SHA256 signature verification for Slack requests
- Role-based permissions (admin vs member channel overrides)

Nango Integration:
- OAuth flow for workspace installation
- Bot token storage and retrieval
- Workspace default agent metadata management

Database (runtime PostgreSQL):
- work_app_slack_workspaces: installed workspaces
- work_app_slack_user_mappings: Slack <> Inkeep user links
- work_app_slack_channel_agent_configs: channel overrides

Frontend (agents-manage-ui):
- Slack dashboard with workspace and channel management
- Channel filters (All, Private, Shared) with visual indicators
- Role-based UI (admin vs member views)
- User linking flow via /link page

API Routes:
- OAuth endpoints: /install, /oauth_redirect
- Event handlers: /commands, /events, /interactions
- Management: /workspaces, /channels, /users

Documentation:
- Comprehensive runbook and architecture docs
- Flow diagrams for OAuth, commands, mentions
- Developer commands and SQL snippets

Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot
Copy link

changeset-bot bot commented Feb 9, 2026

⚠️ No Changeset found

Latest commit: 525dded

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Feb 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agents-api Ready Ready Preview, Comment Feb 11, 2026 8:03pm
agents-manage-ui Ready Ready Preview, Comment Feb 11, 2026 8:03pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
agents-docs Skipped Skipped 💬 7 unresolved Feb 11, 2026 8:03pm

Request Review

@github-actions github-actions bot deleted a comment from claude bot Feb 9, 2026
Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

2 Key Findings | Risk: Medium

This PR introduces a comprehensive Slack Work App integration with solid architecture overall. The security foundations (HMAC verification, JWT tokens, tenant isolation in the data layer) are well-designed. However, there's one critical issue with tenant handling that needs attention before merge.

🔴❗ Critical (1) ❗🔴

🔴 1) nango.ts:133 Hardcoded 'default' tenant bypasses multi-tenancy

Issue: The findWorkspaceConnectionByTeamId function always calls findWorkspaceByTeamIdDb(runDbClient)('default', teamId) with a hardcoded 'default' tenant, ignoring the actual tenant context. This pattern appears in ~30+ locations across the Slack integration.

Why: In a multi-tenant deployment, this breaks tenant isolation guarantees. The workspace lookup would search tenant 'default' regardless of which tenant the request originated from. While Slack teamIds are globally unique (mitigating cross-tenant data leakage risk), this creates:

  • Incorrect tenant associations for workspaces
  • Potential authorization bypass if tenant-specific policies exist
  • Data consistency issues in multi-tenant deployments

Fix: The findWorkspaceConnectionByTeamId function is called in a context where we only have the Slack teamId (from Slack webhook). Two approaches:

  1. Recommended: Look up workspace by teamId without tenant filter first (teamId is unique), then use the workspace's stored tenantId for authorization/subsequent operations
  2. Alternative: Pass through the tenantId from request context where available, accept that initial workspace lookup by teamId may need a tenant-agnostic query

Similar hardcoded 'default' patterns exist in:

  • Route schemas: tenantId: z.string().optional().default('default')
  • Fallback patterns: tenantId || 'default'

Refs: nango.ts:133 · data-access tenant pattern

🟠⚠️ Major (1) 🟠⚠️

🟠 1) handleAppMention, handleCommand, streamAgentResponse Missing unit test coverage for critical runtime paths

Issue: The core Slack event handlers (handleAppMention, handleCommand) and the streaming response logic (streamAgentResponse) lack dedicated unit tests despite being the main entry points for user interactions.

Why: These handlers contain complex logic including:

  • Agent resolution with channel/workspace/user fallbacks
  • Streaming response parsing and Slack block formatting
  • Error handling for timeout, rate limits, and API failures
  • Conversation context management

Without tests, regressions in these critical paths won't be caught until production.

Fix: Add unit tests for:

  1. handleAppMention - test agent resolution logic, error responses, streaming integration
  2. handleCommand - test command parsing, /inkeep link flow, agent selection
  3. streamAgentResponse - test SSE parsing, block assembly, timeout handling

Refs: tests reviewer finding · Existing test patterns in packages/agents-work-apps/src/slack/services/__tests__/

💭 Consider (2) 💭

💭 1) jwt-helpers.ts (not in diff) JwtVerifyResult type allows impossible states

Issue + Why: The existing JwtVerifyResult<T> interface uses valid: boolean with optional payload? and error?, allowing invalid states like { valid: true, error: 'something' }. A discriminated union would provide compile-time safety.

Suggestion: This affects the Slack token implementations. Consider a follow-up PR to refactor to:

export type JwtVerifyResult<T> =
  | { valid: true; payload: T; error?: undefined }
  | { valid: false; payload?: undefined; error: string };

💭 2) packages/agents-work-apps/ New package exports not documented

Issue + Why: The new package adds significant public exports (SlackWorkApp, types, services) but the barrel exports and public API surface aren't documented for internal consumers.

Suggestion: Add JSDoc comments to main exports in src/index.ts and consider adding a brief API section to the README.

📌 Inline Comments (1)

  • 🔴 nango.ts:133 Hardcoded 'default' tenant breaks multi-tenancy

Note: An inline comment was also added to jwt-helpers.ts:24 but this file is not in the PR diff - that finding is captured in Consider above instead.

🕐 Pending Recommendations (2)

From review threads:


💡 APPROVE WITH SUGGESTIONS

Summary: This is a well-architected feature addition with solid security foundations. The HMAC signature verification, JWT token design with proper claims/audiences, and database schema with tenant foreign keys are all implemented correctly. The critical tenant hardcoding issue should be addressed before production deployment in a true multi-tenant environment - though for single-tenant deployments or if multi-tenancy isn't an immediate requirement, this could be tracked as follow-up work. The test coverage gaps are notable but not blocking given the existing integration tests.

Discarded (3)
Location Issue Reason Discarded
jwt-helpers.ts Type inline comment File not in PR diff - cannot add inline comment. Captured in Consider section instead.
security.ts HMAC verification timing Reviewed and found satisfactory - uses timingSafeEqual correctly
Routes Missing rate limiting Pre-existing architectural decision, not introduced by this PR
Reviewers (2)
Reviewer Returned Main Findings Consider Inline Comments Pending Recs Discarded
pr-review-tests 3 1 0 0 0 2
pr-review-types 2 0 1 0 0 1
Manual review 4 1 1 1 0 1
Total 9 2 2 1 0 4

Note: Most automated reviewers (security, architecture, SRE, frontend, standards, devops, docs, breaking-changes) hit context limits due to PR size. Findings above are from reviewers that completed successfully plus targeted manual review.

@github-actions github-actions bot deleted a comment from claude bot Feb 9, 2026
@inkeep inkeep deleted a comment from claude bot Feb 10, 2026
Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

7 Key Findings | Risk: Medium

This is a re-review following 24 commits that addressed most prior feedback. The Slack Work App integration is well-architected overall — the security foundations (HMAC verification, JWT tokens, tenant isolation in the data layer), streaming implementation, and database schema are solid. The remaining findings are primarily around test coverage gaps, consistency refinements, and minor security hardening.

🟠⚠️ Major (4) ⚠️🟠

🟠 1) workspaces.ts:1086-1189 test-message endpoint lacks admin authorization

Issue: The POST /:teamId/test-message endpoint allows any authenticated tenant member to post messages via the bot without requiring org admin/owner privileges — unlike other workspace settings endpoints that use requireWorkspaceAdmin.

Why: A non-admin tenant member could use this endpoint to post messages impersonating the bot. This is inconsistent with the permission model applied to other workspace management operations.

Fix: Apply requireWorkspaceAdmin middleware to this endpoint.

Refs:

🟠 2) modal-submission.ts Modal submission handlers have zero test coverage

Issue: The handleModalSubmission and handleFollowUpSubmission handlers (461 lines) have no test coverage despite being primary user interaction paths for /inkeep private queries.

Why: These handlers contain complex logic: user link validation, thread context handling, agent API calls with 30s timeout, and error recovery paths. Without tests, regressions in modal flows won't be caught until production.

Fix: Add modal-submission.test.ts covering: (1) happy path with mocked workspace/user/agent API, (2) user not linked → link prompt, (3) API timeout → error message, (4) follow-up with existing conversationId.

Refs:

🟠 3) block-actions.ts Block action handlers have zero test coverage

Issue: The handleOpenAgentSelectorModal, handleOpenFollowUpModal, and handleMessageShortcut handlers (291 lines) lack tests. A prior review flagged that handleOpenAgentSelectorModal silently fails without user notification — this fix cannot be verified without tests.

Why: These handlers manage interactive button clicks (Select Agent, Follow Up) and message shortcuts. Without tests, button interactions could silently break.

Fix: Add block-actions.test.ts covering: (1) modal open success, (2) no bot token → early return, (3) no projects → error ephemeral, (4) error during modal open → sendResponseUrlMessage with error.

Refs:

🟠 4) package.json Missing changesets for new Slack integration exports

Issue: This PR adds significant new public exports to @inkeep/agents-work-apps (the ./slack export) and @inkeep/agents-core (Slack JWT utilities, SSE parser), but no changesets cover these additions. The only changeset (fiery-owls-clap.md) is for @inkeep/agents-manage-ui.

Why: Without changesets, the new exports won't be documented in release notes, and semver versioning won't reflect the additions.

Fix:

pnpm bump minor --pkg agents-work-apps "Add Slack Work App integration with OAuth, slash commands, @mention support, and modal interactions"
pnpm bump minor --pkg agents-core "Add Slack JWT utilities for user tokens and link tokens, plus shared SSE parser"

Refs:

Inline comments:

  • 🟠 Major: workspaces.ts:1092 test-message lacks admin authorization

🟡 Minor (7) 🟡

🟡 1) nango.ts Split source of truth creates inconsistency risk

Issue: Workspace default agent is stored in Nango connection metadata, while channel-level configs are stored in PostgreSQL. This split creates consistency risks during failures (no transactional guarantees across systems).

Why: If Nango metadata update succeeds but a related PostgreSQL operation fails, the system ends up inconsistent. This contradicts the architecture note at the top of nango.ts stating "PostgreSQL is the authoritative source of truth."

Fix: Consider storing workspace default agent config in PostgreSQL (add columns to work_app_slack_workspaces: default_project_id, default_agent_id). Use Nango exclusively for OAuth token storage.

Refs:

🟡 2) WaysToTalkToAgent.mdx Slack not listed in overview snippet

Issue: The snippet listing ways to talk to agents (React UI, API, A2A, MCP, Triggers) doesn't mention Slack. Users discovering features via the overview page will miss this major new capability.

Why: The Slack Work App is a first-class way to talk to agents and belongs alongside other methods in the overview.

Fix: Add Slack entry to WaysToTalkToAgent.mdx in both the bullet list and Cards section.

Refs:

🟡 3) strings.ts:11,20-21 "Trigger Agent" terminology mismatch

Issue: Modal titles use "Trigger Agent" terminology while docs use "Ask", "Send", "Prompt" language. "Trigger" suggests webhooks/automation, not user-facing chat.

Why: Creates vocabulary mismatch between the modal UX and documentation. Users opening the modal via /inkeep expect to "Ask" an agent, not "Trigger" one.

Fix: Consider renaming to "Ask Agent" or "Send to Agent" for consistency with docs.

Refs:

🟡 4) entities.ts Missing Slack entity type exports

Issue: Zod validation schemas for Slack tables exist in schemas.ts, but corresponding TypeScript entity types are NOT exported from entities.ts per the data-model-changes checklist.

Why: Downstream code won't have type inference for Slack entities via standard imports, requiring manual inference from Zod schemas.

Fix: Add entity type exports following the established pattern in packages/agents-core/src/types/entities.ts.

Refs:

🟡 5) workspaces.ts OperationId naming diverges from pattern

Issue: Slack routes use slack-{verb}-{noun} format while manage API uses {verb}-{noun}. This introduces a parallel naming scheme.

Why: Inconsistent OpenAPI spec may confuse SDK generators and API consumers.

Fix: This may be intentional namespacing. If so, document the convention for future Work Apps integrations. Otherwise, align with manage API pattern.

Refs:

🟡 6) workspaces.ts Several endpoints return 200 with success:false on errors

Issue: Multiple endpoints (PUT/DELETE channel settings, bulk operations) return HTTP 200 with { success: false } when workspace lookup or tenant verification fails.

Why: HTTP 200 masks the failure. Error handling that relies on status codes would miss these failures. The UI may not properly distinguish between success and failure.

Fix: Return appropriate HTTP status codes (404 for not found, 403 for forbidden) instead of 200 with success:false.

Refs:

🟡 7) app-mention.test.ts Thread-specific flows not tested

Issue: The app-mention tests cover 5 paths but miss thread handling (~40% of handler logic) including bot thread detection, non-bot thread auto-execution with context, and the prompt injection mitigation XML delimiters.

Why: Thread context flows are security-sensitive. The <slack_thread_context> delimiters could be accidentally removed in refactoring without test protection.

Fix: Add tests for thread scenarios: bot thread detection, thread context formatting, XML delimiter presence.

Refs:

Inline comments:

  • 🟡 Minor: events.ts:218-220 Silent early return when workspace lookup fails
  • 🟡 Minor: nango.ts:27-31 Workspace connection cache unbounded
  • 🟡 Minor: 0012_add-slack-work-app-tables.sql:52-60 Migration contains DROP CONSTRAINT artifacts
  • 🟡 Minor: package.json:71 Repository directory typo

💭 Consider (3) 💭

💭 1) slack-api.ts:1 UI API client diverges from established pattern

Issue: The file uses raw fetch() calls instead of makeManagementApiRequest() from api-config.ts, missing cookie forwarding, error normalization, and bypass secret injection.

Fix: Refactor to use makeManagementApiRequest() or create a makeWorkAppsApiRequest() helper.

Refs: Prior review discussion

💭 2) modals.ts:102,123 Project concept exposed in modal UX

Issue: The agent picker modal requires selecting a "Project" then an "Agent", but docs focus only on agents. Users may not understand the Project concept.

Fix: Consider auto-selecting the project when only one exists, or adding brief explanation in docs.

💭 3) commands/index.ts:83 Nested fetch calls share single AbortController timeout

Issue: A single 10-second AbortController is used for both projects fetch and all subsequent agent fetches. If projects takes 8 seconds, agents only get 2 seconds.

Fix: Consider separate AbortControllers per operation group, or increase overall timeout.

🕐 Pending Recommendations (1)

  • 💭 slack-api.ts:1 UI API client pattern divergence (from prior review)

💡 APPROVE WITH SUGGESTIONS

Summary: This is a well-architected Slack integration that addresses most prior review feedback. The core security patterns (HMAC, JWT, tenant isolation), streaming implementation, and database schema are solid. The main gaps are test coverage for modal/block-action handlers and some consistency refinements. The changesets should be added before merge to ensure proper release notes. None of the findings are blocking for functionality, but the test coverage gaps represent meaningful risk for a user-facing integration of this scope.

Discarded (12)
Location Issue Reason Discarded
Architecture Schema design analysis Positive observation, not an issue
Architecture Package structure analysis Positive observation, not an issue
Architecture Agent resolution extensibility Low confidence, speculative future concern
Architecture Cross-DB referential integrity Acknowledged known limitation, documented in codebase
Tests commands.test.ts redundant INFO level, not actionable
DevOps tsconfig redundant include INFO level, trivial
DevOps vitest config thread pool INFO level, not critical for correctness
Consistency dev bypass helper extraction INFO level, code style preference
Docs No issues found Documentation well-structured
Standards No issues found Prior issues addressed
Errors Silent JSON parse catches Low confidence, defensive coding pattern
nango.ts:133 Hardcoded 'default' tenant ADDRESSED in commit 1575710b
Reviewers (13)
Reviewer Returned Main Findings Consider Inline Comments Pending Recs Discarded
pr-review-standards 0 0 0 0 0 0
pr-review-security-iam 2 1 0 1 0 0
pr-review-sre 3 0 1 1 0 1
pr-review-architecture 7 1 0 0 0 6
pr-review-frontend 1 0 1 0 1 0
pr-review-tests 6 3 0 0 0 1
pr-review-types 1 0 0 0 0 1
pr-review-docs 0 0 0 0 0 0
pr-review-product 3 2 1 0 0 0
pr-review-consistency 6 2 0 1 0 1
pr-review-breaking-changes 2 1 0 1 0 0
pr-review-errors 10 1 0 0 0 1
pr-review-devops 6 1 0 1 0 2
Total 47 12 3 5 1 13

Note: All 13 reviewers completed successfully. Prior Critical finding (hardcoded 'default' tenant) was addressed in commit 1575710b.

path: '/:teamId/test-message',
summary: 'Send Test Message',
description: 'Send a test message to verify the bot is working correctly.',
operationId: 'slack-test-message',
Copy link
Contributor

Choose a reason for hiding this comment

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

MAJOR POST /:teamId/test-message lacks admin authorization

Issue: The test-message endpoint allows any authenticated tenant member to post arbitrary messages to any Slack channel via the bot. While it verifies verifyTenantOwnership, it doesn't require org admin/owner privileges like other workspace settings endpoints.

Why: A non-admin tenant member could use this endpoint to post messages impersonating the bot in any channel where the bot is installed. This is inconsistent with other workspace management endpoints that use requireWorkspaceAdmin.

Fix: Apply requireWorkspaceAdmin middleware to this endpoint, consistent with other workspace settings routes:

// Add middleware before the route handler (around line 1080)
app.use('/:teamId/test-message', async (c, next) => {
  if (c.req.method === 'POST') {
    return requireWorkspaceAdmin()(c, next);
  }
  return next();
});

Refs:

Comment on lines +218 to +220

const workspace = await findWorkspaceConnectionByTeamId(teamId);
if (!workspace?.botToken) return;
Copy link
Contributor

Choose a reason for hiding this comment

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

MINOR Silent early return when workspace lookup fails

Issue: When findWorkspaceConnectionByTeamId returns null or a workspace without botToken, the handler returns silently with no logging. Users see their project selection appear to succeed but the agent dropdown never refreshes.

Why: Makes debugging user-reported issues ("agent picker doesn't update") difficult since there's no log trail indicating why the operation was skipped.

Fix: Add warning log when workspace or bot token is not found:

Suggested change
const workspace = await findWorkspaceConnectionByTeamId(teamId);
if (!workspace?.botToken) return;
const workspace = await findWorkspaceConnectionByTeamId(teamId);
if (!workspace?.botToken) {
logger.warn({ teamId, selectedProjectId }, 'Cannot update modal: workspace not found or missing botToken');
return;
}

Refs:

Comment on lines +27 to +31
const workspaceConnectionCache = new Map<
string,
{ connection: SlackWorkspaceConnection; expiresAt: number }
>();
const CACHE_TTL_MS = 60_000;
Copy link
Contributor

Choose a reason for hiding this comment

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

MINOR Workspace connection cache unbounded unlike user mapping cache

Issue: The workspaceConnectionCache is a simple Map without size bounds. The user mapping cache in utils.ts was updated with a max size of 5000 entries and eviction logic, but this cache lacks similar bounds.

Why: In a multi-tenant deployment with many Slack workspaces, this cache could grow unbounded. While the 60-second TTL provides some natural eviction, under high load with many unique teamIds, memory can grow significantly before TTL evictions occur.

Fix: Consider adding a max size constant and eviction logic similar to userMappingCache in utils.ts:

const MAX_WORKSPACE_CACHE_SIZE = 1000;

// In findWorkspaceConnectionByTeamId, before adding to cache:
if (workspaceConnectionCache.size >= MAX_WORKSPACE_CACHE_SIZE) {
  const oldestKey = workspaceConnectionCache.keys().next().value;
  if (oldestKey) workspaceConnectionCache.delete(oldestKey);
}

Refs:

Comment on lines +52 to +60
/*
Unfortunately in current drizzle-kit version we can't automatically get name for primary key.
We are working on making it available!

Meanwhile you can:
1. Check pk name in your database, by running
SELECT constraint_name FROM information_schema.table_constraints
WHERE table_schema = 'public'
AND table_name = 'work_app_github_installations'
Copy link
Contributor

Choose a reason for hiding this comment

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

MINOR Migration contains Drizzle-generated DROP CONSTRAINT artifacts

Issue: The migration file includes commented-out DROP CONSTRAINT statements for existing GitHub tables that Drizzle auto-generated but could not resolve:

-- ALTER TABLE "work_app_github_installations" DROP CONSTRAINT ...
-- ALTER TABLE "work_app_github_mcp_tool_repository_access" DROP CONSTRAINT ...

Why: These are Drizzle-kit artifacts from schema drift detection that should not be in the migration. While harmless (commented out), they indicate potential schema drift and could confuse future maintainers who might uncomment them.

Fix: Remove the commented-out DROP CONSTRAINT blocks entirely. They serve no purpose and may indicate schema drift worth investigating with pnpm db:check.

Refs:

…-scoped conversation IDs

- add dispatch_action to project dropdown for dynamic agent list updates via views.update
- validate agent selection on modal submit, show inline error when no agents available
- scope conversationId by agentId to prevent sub-agent conflicts when switching agents in same thread
- apply to both agent selector and message shortcut modals
- update OpenAPI snapshot for removed resources routes and renamed workspaceId param
- fix biome formatting across all touched files
Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

5 Key Findings | Risk: Medium

🔴❗ Critical (0) ❗🔴

No critical issues identified. Prior reviews have addressed most security concerns (OAuth state signing, tenant isolation, auth bypass).

🟠⚠️ Major (2) 🟠⚠️

🟠 1) modal-submission.ts · block-actions.ts Modal/block action handlers have zero test coverage

Issue: Two core user interaction paths have no test coverage:

  • modal-submission.ts (461 lines) — handles the private query flow via /inkeep slash commands
  • block-actions.ts (291 lines) — handles button clicks for agent selection and follow-up queries

Why: These are primary UX paths where regressions would silently break the Slack experience. Specific risks include:

  • 30-second AbortController timeout logic could fail to abort properly
  • JSON parsing of modal metadata could throw uncaught exceptions
  • Error classification could return incorrect error types
  • Agent selector fallback to workspace default could break

Fix: Add dedicated test files covering:

  1. Happy path: valid metadata + linked user + successful API response → ephemeral message posted
  2. Missing user link: → returns 'link your account' ephemeral
  3. API timeout: mock fetch to abort after 30s → returns timeout error message
  4. Follow-up submission: verify conversationId is passed correctly

Refs:


🟠 2) packages/agents-work-apps/package.json Missing changeset for new Slack package exports

Issue: This PR adds a new ./slack export to @inkeep/agents-work-apps which is a published package (publishConfig.access: public). No changeset documents this addition.

Why: Without a changeset:

  • The new exports won't appear in release notes
  • The npm version won't reflect the feature addition (should be minor bump)
  • Consumers upgrading won't know about the new Slack integration APIs

Fix:

pnpm bump minor --pkg agents-work-apps "Add Slack Work App integration with OAuth, slash commands, and @mention support"

If @inkeep/agents-core also gained new exports (Slack JWT utilities), include that package as well.

Refs:

Inline comments:

  • 🟠 Major: block-actions.ts:156-168 handleOpenFollowUpModal catches errors without user notification

🟡 Minor (3) 🟡

🟡 1) slack-api.ts UI API client diverges from established pattern

Issue: The Slack API client uses raw fetch() calls directly instead of following the established pattern. All other API clients in /lib/api/ use makeManagementApiRequest() from api-config.ts which handles cookie forwarding, error normalization, and bypass secret injection. The file also lacks the 'use server' directive present in peer API files.

Why: Inconsistency with established patterns makes the codebase harder to maintain and could lead to subtle auth/error handling bugs.

Fix: Refactor to use makeManagementApiRequest() with appropriate path handling for work-apps routes, add 'use server' directive, and follow the function export pattern from github.ts.

Refs:


🟡 2) workspaces.ts:4-13 Header comment lists 9 endpoints but file has 13

Issue: The header comment's route listing is incomplete. Missing:

  • PUT /:teamId/channels/bulk (Bulk Set Channel Agents)
  • DELETE /:teamId/channels/bulk (Bulk Remove Channel Configs)
  • GET /:teamId/health (Check Workspace Health)
  • POST /:teamId/test-message (Send Test Message)

Why: Developers consulting the header comment would miss 4 significant endpoints, making the file harder to navigate.

Fix: Update the header comment to include all 13 routes.

Refs:


🟡 3) env.ts / .env.example Env var sync drift

Issue: SLACK_BOT_TOKEN is defined in env.ts but missing from .env.example. Additionally, NANGO_SLACK_INTEGRATION_ID appears required for OAuth but is marked optional in the schema.

Why: New developers setting up the Slack integration may not realize which env vars are truly required for the OAuth flow to work.

Fix: Add SLACK_BOT_TOKEN to .env.example and consider adding .describe() to clarify when each Slack env var is required.

Refs:

Inline comments:

  • 🟡 Minor: client.ts:21 Slack WebClient calls lack explicit timeout configuration
  • 🟡 Minor: slack-link-token.ts:56-61 JSDoc references stale "device authorization flow" terminology
  • 🟡 Minor: blocks/index.ts:91-99 Agent list truncation at 15 items without clear path to see more
  • 🟡 Minor: commands/index.ts:731-742 Command aliases may fragment user mental models
  • 🟡 Minor: installation.mdx:14-15 Prerequisites lack env var documentation link

💭 Consider (4) 💭

💭 1) workspaces.ts operationId naming inconsistency
Issue: Slack routes use slack-<action> format while manage API GitHub routes use <verb>-github-<noun> format.
Fix: Document as intended pattern evolution or align for consistency.

💭 2) types.ts WorkAppsVariables interface allows illegal states
Issue: All optional fields allow invalid combinations (e.g., tenantRole without tenantId).
Fix: Consider discriminated union to represent valid authentication states.

💭 3) agent-configuration-card.tsx:149 Waterfall fetch pattern
Issue: Three sequential network requests on mount (fetchAgents, fetchChannels, fetchWorkspaceSettings).
Fix: Use Promise.all for coordinated loading like workspace-hero.tsx does.

💭 4) streaming.ts:233 Stream reader not explicitly released on error
Issue: When an error occurs in the stream processing loop, the reader from response.body.getReader() is not explicitly released.
Fix: Add await reader.cancel() in the catch block.

🕐 Pending Recommendations (4)


💡 APPROVE WITH SUGGESTIONS

Summary: This is a well-structured, comprehensive Slack Work App integration that follows established patterns and includes good security foundations. The main gaps are test coverage for modal/block action handlers and a missing changeset for the new package exports. Prior reviews have successfully addressed most security concerns. Addressing the Major findings (test coverage, changeset) and the Pending Recommendations would strengthen this integration before release. 🎉

Discarded (12)
Location Issue Reason Discarded
types.ts SlackCommandResponse allows invalid empty responses Matches Slack SDK patterns; INFO severity, acceptable tradeoff
types.ts WorkApp interface optional fields UI handles gracefully; MEDIUM confidence
agent-configuration-card.tsx Long list without virtualization LOW confidence; typical workspaces have <50 channels
linked-users-section.tsx Component defined inside component LOW confidence; minimal performance impact
sse-parser.ts SSE parser silently ignores malformed data Acceptable for streaming; partial chunks expected
streaming.ts Stream text accumulation unbounded Upstream enforces max_tokens; LOW confidence
slack-dashboard.tsx URL parameter reads window.location Works correctly; stylistic preference
nango.ts Dual source of truth for workspace config Architecture decision; MEDIUM confidence
slack/ Work Apps package introduces direct DB dependency Follows existing GitHub pattern
runtime-schema.ts Channel configs lack FK constraints Acceptable for dual-DB architecture
slack.mdx / work-apps.mdx Duplicate OpenAPI-generated content Auto-generated; fix would be in OpenAPI spec
commands.mdx Steps not using <Steps> component Reference page; numbered list acceptable
Reviewers (14)
Reviewer Returned Main Findings Consider Inline Comments Pending Recs Discarded
pr-review-tests 5 1 0 0 0 0
pr-review-errors 7 1 0 1 0 0
pr-review-consistency 8 1 1 0 0 1
pr-review-devops 4 1 0 0 1 0
pr-review-product 5 0 0 2 0 1
pr-review-comments 3 1 0 1 0 0
pr-review-sre 4 0 1 1 1 0
pr-review-frontend 6 0 1 0 0 3
pr-review-docs 5 0 0 1 0 2
pr-review-types 3 0 1 0 0 2
pr-review-llm 3 0 1 0 0 2
pr-review-architecture 5 0 0 0 1 3
pr-review-standards 0 0 0 0 0 0
pr-review-security-iam 0 0 0 0 0 0
Total 58 5 5 6 4 12

Note: pr-review-standards and pr-review-security-iam returned 0 findings as all issues were already covered by prior reviews.

*/
export function getSlackClient(token: string): WebClient {
return new WebClient(token);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

MINOR Slack WebClient calls lack explicit timeout configuration

Issue: The getSlackClient() function creates a new WebClient(token) without configuring a timeout. All Slack API calls (users.info, team.info, conversations.list, chat.postMessage, etc.) rely on the SDK's default behavior which may not have a timeout or may have a very long one.

Why: If Slack's API is slow or unresponsive, these calls can block indefinitely. During @mention handling and modal submissions, hung Slack API calls would prevent users from receiving responses and could exhaust server resources under load.

Fix:

Suggested change
}
export function getSlackClient(token: string): WebClient {
return new WebClient(token, {
timeout: 10_000, // 10 second timeout for Slack API calls
});
}

Refs:

Comment on lines +56 to +61
/**
* Sign a Slack link JWT token for the device authorization flow.
* Token expires in 10 minutes.
*
* This token is generated when a user runs `/inkeep link` in Slack
* and is verified when they visit the dashboard link page.
Copy link
Contributor

Choose a reason for hiding this comment

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

MINOR JSDoc references stale "device authorization flow" terminology

Issue: The comment states "Sign a Slack link JWT token for the device authorization flow." However, this is NOT OAuth Device Authorization Grant (RFC 8628). The actual flow is a custom user-initiated linking flow where users run /inkeep link in Slack and visit a dashboard URL.

Why: A developer familiar with OAuth standards would incorrectly assume this implements RFC 8628, leading to confusion about the security properties (device codes, polling intervals) that don't exist here.

Fix:

Suggested change
/**
* Sign a Slack link JWT token for the device authorization flow.
* Token expires in 10 minutes.
*
* This token is generated when a user runs `/inkeep link` in Slack
* and is verified when they visit the dashboard link page.
/**
* Sign a Slack link JWT token for the Slack-to-Inkeep account linking flow.
* Token expires in 10 minutes.
*
* This token is generated when a user runs `/inkeep link` in Slack
* and is verified when they visit the dashboard link page.
*/

Refs:

Comment on lines +156 to +168
return;
}

const slackClient = getSlackClient(workspaceConnection.botToken);
const modal = buildFollowUpModal(metadata);

await slackClient.views.open({
trigger_id: triggerId,
view: modal,
});

logger.info(
{ teamId, conversationId: metadata.conversationId, agentId: metadata.agentId },
Copy link
Contributor

Choose a reason for hiding this comment

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

MAJOR handleOpenFollowUpModal catches errors without user notification

Issue: This function catches all errors but only logs them without notifying the user. When the catch block executes, the function returns implicitly with no user feedback. Users click the "Follow Up" button and see nothing happen.

Why: Unlike handleOpenAgentSelectorModal (line 129-135) and handleMessageShortcut (lines 287-291) which notify users via responseUrl, this handler has no responseUrl parameter to provide feedback. This creates a dead-end UX where the most important action in multi-turn conversations silently fails.

Fix: Pass responseUrl from the block_actions payload to this handler and use it for error notification:

export async function handleOpenFollowUpModal(params: {
  triggerId: string;
  actionValue: string;
  teamId: string;
  responseUrl?: string;  // Add this parameter
}): Promise<void> {
  // ...existing code...
  } catch (error) {
    logger.error({ error, teamId }, 'Failed to open follow-up modal');
    if (params.responseUrl) {
      await sendResponseUrlMessage(params.responseUrl, {
        text: 'Failed to open follow-up dialog. Please try again.',
        response_type: 'ephemeral',
      }).catch(() => {});
    }
  }
}

Refs:

@github-actions github-actions bot deleted a comment from claude bot Feb 11, 2026
- @nangohq/node, @slack/bolt, @slack/web-api, slack-block-builder, @slack/types
- these are used by agents-work-apps, not agents-api directly
Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

0 Key Findings | Risk: None

This delta review covers 1 commit (216e76c4) since the last automated review, with 1 file changed (agents-api/package.json, -5 lines).

Delta Assessment

Clean dependency cleanup — The commit removes 5 unused Slack-related dependencies from agents-api/package.json:

  • @nangohq/node (production)
  • @slack/bolt (production)
  • @slack/web-api (production)
  • slack-block-builder (production)
  • @slack/types (devDependencies)

Verified correct: These dependencies are properly declared in packages/agents-work-apps/package.json where the Slack integration code resides. Since agents-api depends on @inkeep/agents-work-apps: workspace:^, it receives transitive access to these packages through the workspace dependency.

🔴❗ Critical (0) ❗🔴

None.

🟠⚠️ Major (0) 🟠⚠️

None.

🟡 Minor (0) 🟡

None.

🕐 Pending Recommendations (18+)

These ACTIVE findings from prior reviews remain unaddressed on unchanged code paths. They are outside this delta's scope but should be addressed before merge:

Major (6):

  • 🟠 app-mention.ts:179-196 Unsanitized thread context enables indirect prompt injection
  • 🟠 permissions.ts:72-91 Permission middleware blocks session-based users due to premature tenantId check
  • 🟠 workspaces.ts:248 GET /:teamId/settings lacks tenant ownership verification
  • 🟠 events.ts:376 Nango webhook endpoint lacks signature verification
  • 🟠 users.ts:143 User link verification accepts arbitrary userId from request body
  • 🟠 workspaces.ts:1095 POST /:teamId/test-message lacks admin authorization

Minor (12+):


💡 APPROVE WITH SUGGESTIONS

Summary: The delta is a clean dependency cleanup with no functional changes. The removed dependencies are verified to exist in the correct package (agents-work-apps). However, 18+ ACTIVE prior findings remain unaddressed on unchanged code paths — including 6 Major issues around tenant isolation, authorization, and prompt injection. These should be reviewed and addressed before merge. The dependency cleanup itself is ✅ good to go.

Reviewers (0)
Reviewer Returned Main Findings Consider Inline Comments Pending Recs Discarded
(none dispatched)

Note: No domain reviewers were dispatched for this delta due to trivial scope (pure dependency cleanup with no code changes).

@github-actions github-actions bot deleted a comment from claude bot Feb 11, 2026
- delete unused work-apps/index.ts and slack/api/index.ts barrel files
- remove unused component re-exports from components/index.ts (keep only SlackDashboard)
- make SlackWorkspaceInstallation and DefaultAgentConfig non-exported (used internally only)
Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

0 Key Findings | Risk: Low

Re-Review Delta Assessment

This re-review covers 1 commit since the last automated review:

  • 525dded9fix: remove unused barrel exports and files flagged by knip

Delta scope: 4 files (2 deleted, 2 modified) — pure cleanup removing unused exports. No functional changes.

File Change
agents-manage-ui/src/features/work-apps/index.ts Deleted (unused barrel)
agents-manage-ui/src/features/work-apps/slack/api/index.ts Deleted (unused barrel)
agents-manage-ui/src/features/work-apps/slack/api/slack-api.ts Interfaces made non-exported
agents-manage-ui/src/features/work-apps/slack/components/index.ts Reduced exports to only SlackDashboard

Verification complete:

  • ✅ Deleted barrel files were not imported anywhere externally
  • ✅ Made-private interfaces (SlackWorkspaceInstallation, DefaultAgentConfig) are only used internally
  • ✅ Removed component exports were only used internally within slack-dashboard.tsx
  • ✅ Change is consistent with established patterns in the codebase

No new issues identified in the delta.


🕐 Pending Recommendations (11)

The following issues were raised in prior reviews and remain ACTIVE (unaddressed):

Security & Authorization (2 Major)

Security Considerations (1 Major)

Authorization Flow (1 Major)

Minor Items (6)


💡 APPROVE WITH SUGGESTIONS

Summary: The delta cleanup is correct and introduces no new issues. The knip-flagged unused exports have been properly removed without breaking any imports. However, 11 prior review findings remain unaddressed — including several Major security/authorization concerns (tenant isolation gaps, permission middleware ordering, prompt injection risk). These should be addressed before merging to ensure the Slack Work App integration maintains proper security boundaries.


Reviewers (3)
Reviewer Returned Main Findings Consider Inline Comments Pending Recs Discarded
pr-review-standards 0 0 0 0 0 0
pr-review-consistency 0 0 0 0 0 0
pr-review-frontend 0 0 0 0 0 0
Total 0 0 0 0 11 0

Note: All reviewers confirmed the delta cleanup is correct. Pending recommendations are from prior automated review threads that remain ACTIVE.

@github-actions github-actions bot deleted a comment from claude bot Feb 11, 2026
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 participants