twentyhq/twenty · error · Error

No workspace has claimed Slack team ${teamId}. Connect the S

Error message

No workspace has claimed Slack team ${teamId}. Connect the Slack app in the target workspace first.

What it means

The Slack logic function resolves the target Twenty workspace by reading the `team_id` from the Slack event and looking up `kv.get(getSlackTeamKvKey(teamId), { scope: 'SERVER' })`. If no value is stored, no Twenty workspace has connected/claimed that Slack team, so the event cannot be routed and the handler throws. The claim is written when a workspace connects the Slack app, so absence means the connect step was never completed for this Slack team.

Source

Thrown at packages/twenty-apps/public/slack/src/logic-functions/utils/resolve-target-workspace-id.ts:23

import { getSlackTeamKvKey } from 'src/logic-functions/utils/get-slack-team-kv-key';

export const resolveTargetWorkspaceId = async (
  body: SlackEventsRequestBody,
): Promise<string> => {
  const teamId = body.team_id;

  if (!isNonEmptyString(teamId)) {
    throw new Error(
      'Slack event has no team_id; cannot resolve the target workspace',
    );
  }

  const claimedWorkspaceId = await kv.get<string>(getSlackTeamKvKey(teamId), {
    scope: 'SERVER',
  });

  if (!isNonEmptyString(claimedWorkspaceId)) {
    throw new Error(
      `No workspace has claimed Slack team ${teamId}. Connect the Slack app in the target workspace first.`,
    );
  }

  return claimedWorkspaceId;
};

View on GitHub (pinned to 1f5dd2bbd2)

Solutions

  1. In the target Twenty workspace, connect the Slack app (Settings → Apps → Slack) so the connect handler writes the `team_id → workspaceId` KV entry at server scope.
  2. Confirm `getSlackTeamKvKey(teamId)` produces the same key at read and write time (no refactor changed the key format).
  3. Verify the KV entry is stored at `scope: 'SERVER'` on both the connect and resolve paths.
  4. If KV was reset, re-run the Slack connect flow in each workspace to repopulate the claim entries.

Example fix

// before
const claimedWorkspaceId = await kv.get<string>(getSlackTeamKvKey(teamId), { scope: 'SERVER' });
if (!isNonEmptyString(claimedWorkspaceId)) {
  throw new Error(`No workspace has claimed Slack team ${teamId}. Connect the Slack app in the target workspace first.`);
}

// after — return a user-facing error response instead of throwing for unconnected teams
const claimedWorkspaceId = await kv.get<string>(getSlackTeamKvKey(teamId), { scope: 'SERVER' });
if (!isNonEmptyString(claimedWorkspaceId)) {
  return {
    text: `This Slack workspace (${teamId}) isn't connected to a Twenty workspace yet. Connect the Slack app in Twenty → Settings → Apps first.`,
    response_type: 'ephemeral',
  };
}
Defensive patterns

Strategy: validation

Validate before calling

// Health check the Slack app can expose:
export const isSlackTeamClaimed = async (teamId: string): Promise<boolean> =>
  isNonEmptyString(await kv.get<string>(getSlackTeamKvKey(teamId), { scope: 'SERVER' }));

Type guard

const isClaimedWorkspaceId = (v: unknown): v is string =>
  typeof v === 'string' && v.trim().length > 0;

Prevention

When it happens

Trigger: A Slack event arrives from a `team_id` that was never connected to a Twenty workspace, was connected but the KV entry expired/was cleared, or the lookup uses a different scope/key than the connect step wrote.

Common situations: A user in a Slack workspace that hasn't installed/connected the Twenty Slack app posting a slash command or event; KV being flushed/reset; a key-formation mismatch between `getSlackTeamKvKey` at write time and read time after a refactor.

Related errors


AI-assisted analysis of twentyhq/twenty@1f5dd2bbd2 (2026-08-12). Data as JSON: /api/errors/941d93308ac918fb. Report an issue: GitHub.