Fix AI chat threads query firing for users without AI permissions (#19507)

<img width="1511" height="825" alt="Capture d’écran 2026-04-09 à 14 19
52"
src="https://github.com/user-attachments/assets/cd6c533e-abc9-45f9-b5c1-5cb7341d5dfe"
/>

- Move GetChatThreads query from the generic metadata pipeline
(useLoadStaleMetadataEntities) into AgentChatThreadInitializationEffect,
gated by useHasPermissionFlag(AI_SETTINGS) — prevents "Entity does not
have permissions" errors for users whose role lacks AI access
- Fix initialization bug where isDefined(currentAIChatThread) always
returned true for the 'unknown-thread' sentinel value, preventing thread
initialization from ever running — replaced with isValidUuid check
This commit is contained in:
Thomas Trompette
2026-04-09 17:17:30 +02:00
committed by GitHub
parent 0c7712926d
commit 082822b790
2 changed files with 53 additions and 34 deletions
@@ -1,6 +1,6 @@
import { useAtomValue, useStore } from 'jotai';
import { useEffect } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { isDefined, isValidUuid } from 'twenty-shared/utils';
import {
AGENT_CHAT_NEW_THREAD_DRAFT_KEY,
@@ -14,13 +14,26 @@ import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState';
import { currentAIChatThreadTitleState } from '@/ai/states/currentAIChatThreadTitleState';
import { hasInitializedAgentChatThreadsState } from '@/ai/states/hasInitializedAgentChatThreadsState';
import { hasTriggeredCreateForDraftState } from '@/ai/states/hasTriggeredCreateForDraftState';
import { useUpdateMetadataStoreDraft } from '@/metadata-store/hooks/useUpdateMetadataStoreDraft';
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
import { type FlatAgentChatThread } from '@/metadata-store/types/FlatAgentChatThread';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { useApolloClient } from '@apollo/client/react';
import {
GetChatThreadsDocument,
PermissionFlagType,
} from '~/generated-metadata/graphql';
export const AgentChatThreadInitializationEffect = () => {
const client = useApolloClient();
const { replaceDraft, applyChanges } = useUpdateMetadataStoreDraft();
const hasAiSettingsPermission = useHasPermissionFlag(
PermissionFlagType.AI_SETTINGS,
);
const currentAIChatThread = useAtomStateValue(currentAIChatThreadState);
const setCurrentAIChatThread = useSetAtomState(currentAIChatThreadState);
const setAgentChatInput = useSetAtomState(agentChatInputState);
@@ -40,15 +53,46 @@ export const AgentChatThreadInitializationEffect = () => {
useAtomState(hasInitializedAgentChatThreadsState);
useEffect(() => {
setAgentChatThreadsLoading(storeEntry.status === 'empty');
}, [storeEntry.status, setAgentChatThreadsLoading]);
useEffect(() => {
if (hasInitializedAgentChatThreads || isDefined(currentAIChatThread)) {
if (storeEntry.status !== 'empty' || !hasAiSettingsPermission) {
return;
}
if (storeEntry.status === 'empty') {
client
.query({
query: GetChatThreadsDocument,
variables: { paging: { first: 500 } },
fetchPolicy: 'network-only',
})
.then((result) => {
if (!isDefined(result.data?.chatThreads?.edges)) {
return;
}
const threads = result.data.chatThreads.edges.map((edge) => edge.node);
replaceDraft('agentChatThreads', threads);
applyChanges();
});
}, [
storeEntry.status,
hasAiSettingsPermission,
client,
replaceDraft,
applyChanges,
]);
useEffect(() => {
setAgentChatThreadsLoading(
storeEntry.status === 'empty' && hasAiSettingsPermission,
);
}, [storeEntry.status, hasAiSettingsPermission, setAgentChatThreadsLoading]);
useEffect(() => {
if (hasInitializedAgentChatThreads || isValidUuid(currentAIChatThread)) {
return;
}
if (storeEntry.status === 'empty' && hasAiSettingsPermission) {
return;
}
@@ -99,6 +143,7 @@ export const AgentChatThreadInitializationEffect = () => {
}, [
agentChatThreads,
currentAIChatThread,
hasAiSettingsPermission,
hasInitializedAgentChatThreads,
setHasInitializedAgentChatThreads,
storeEntry.status,
@@ -5,12 +5,10 @@ import { splitPageLayoutWithRelated } from '@/metadata-store/utils/splitPageLayo
import { splitViewWithRelated } from '@/metadata-store/utils/splitViewWithRelated';
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
import { transformPageLayout } from '@/page-layout/utils/transformPageLayout';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useApolloClient } from '@apollo/client/react';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
import {
FeatureFlagKey,
FindAllViewsDocument,
FindManyCommandMenuItemsDocument,
FindAllRecordPageLayoutsDocument,
@@ -18,7 +16,6 @@ import {
FindManyFrontComponentsDocument,
FindManyLogicFunctionsDocument,
FindManyNavigationMenuItemsDocument,
GetChatThreadsDocument,
type ObjectMetadataItemsQuery,
ViewType,
} from '~/generated-metadata/graphql';
@@ -56,7 +53,6 @@ const hasOverlap = (
export const useLoadStaleMetadataEntities = () => {
const client = useApolloClient();
const { replaceDraft, applyChanges } = useUpdateMetadataStoreDraft();
const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
const loadStaleMetadataEntities = useCallback(
async (staleEntityKeys: MetadataEntityKey[]) => {
@@ -226,32 +222,10 @@ export const useLoadStaleMetadataEntities = () => {
);
}
if (staleEntityKeys.includes('agentChatThreads') && isAiEnabled) {
fetchPromises.push(
client
.query({
query: GetChatThreadsDocument,
variables: { paging: { first: 500 } },
fetchPolicy: 'network-only',
})
.then((result) => {
if (!isDefined(result.data?.chatThreads?.edges)) {
return;
}
const threads = result.data.chatThreads.edges.map(
(edge) => edge.node,
);
replaceDraft('agentChatThreads', threads);
}),
);
}
await Promise.all(fetchPromises);
applyChanges();
},
[client, replaceDraft, applyChanges, isAiEnabled],
[client, replaceDraft, applyChanges],
);
return { loadStaleMetadataEntities };