{"record":{"id":"754e806f85b8b7b3","repo":"toeverything/AFFiNE","slug":"copilot-prompt-invalid","errorCode":"copilot_prompt_invalid","errorMessage":"${promptName} are not allowed for ${sessionType} sessions","messagePattern":"(.+?) are not allowed for (.+?) sessions","errorType":"exception","errorClass":"CopilotPromptInvalid","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/models/copilot-session.ts","lineNumber":420,"sourceCode":"  getSessionType(session: Pick<ChatSession, 'docId' | 'pinned'>): SessionType {\n    if (session.pinned) return SessionType.Pinned;\n    if (!session.docId) return SessionType.Workspace;\n    return SessionType.Doc;\n  }\n\n  checkSessionPrompt(\n    session: Pick<ChatSession, 'docId' | 'pinned'>,\n    prompt: Partial<ChatPrompt>\n  ): boolean {\n    const sessionType = this.getSessionType(session);\n    const { name: promptName, action: promptAction } = prompt;\n\n    // workspace and pinned sessions cannot use action prompts\n    if (\n      [SessionType.Workspace, SessionType.Pinned].includes(sessionType) &&\n      !!promptAction?.trim()\n    ) {\n      throw new CopilotPromptInvalid(\n        `${promptName} are not allowed for ${sessionType} sessions`\n      );\n    }\n\n    return true;\n  }\n\n  @Transactional()\n  async create(state: ChatSession, reuseChat = false): Promise<string> {\n    await this.lockPersonalScope(\n      state.workspaceId,\n      state.userId,\n      state.personal\n    );\n    // find and return existing session if session is chat session\n    if (reuseChat && !state.promptAction) {\n      const sessionId = await this.find(state);\n      if (sessionId) return sessionId;","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/models/copilot-session.ts#L402-L438","documentation":"CopilotPromptInvalid, thrown by CopilotSessionModel.checkSessionPrompt. The session type is derived from the session shape (getSessionType at copilot-session.ts:350): pinned -> Pinned, no docId -> Workspace, otherwise Doc. Prompts carrying a non-empty action (prompt.action?.trim() is truthy) are only valid for Doc sessions, because the action binds to a specific doc's context. Applying an action prompt to a workspace-level or pinned session is rejected before the session is created or updated.","triggerScenarios":"Calling a create/update flow that runs checkSessionPrompt with a session where pinned is true or docId is null/undefined, while the prompt object has a non-blank action string. The message interpolates the prompt name and the resolved session type.","commonSituations":"Frontend reusing a doc action prompt (e.g. an 'improve this doc' action) for the workspace-level copilot panel; applying the last-used prompt payload to a pinned session; prompt templates that default action to a stale value.","solutions":["Strip the action (send action: '' or omit it) when the session has no docId or is pinned","Attach the session to a doc (pass docId) so getSessionType returns Doc and action prompts are allowed","Unpin the session or choose a regular doc chat session before applying the action prompt"],"exampleFix":"// before\n// state has no docId -> Workspace session\nawait sessions.create({ ...state, prompt: { name: 'edit-existing', action: 'edit-existing' } });\n\n// after\nconst type = state.pinned ? 'pinned' : state.docId ? 'doc' : 'workspace';\nconst prompt = type === 'doc'\n  ? { name: 'edit-existing', action: 'edit-existing' }\n  : { name: 'edit-existing' }; // no action outside doc sessions\nawait sessions.create({ ...state, prompt });","handlingStrategy":"validation","validationCode":"function sessionTypeOf(s: { docId?: string | null; pinned?: boolean | null }) {\n  if (s.pinned) return 'pinned';\n  return s.docId ? 'doc' : 'workspace';\n}\n\nfunction promptFitsSession(\n  prompt: { action?: string | null },\n  session: { docId?: string | null; pinned?: boolean | null }\n) {\n  return sessionTypeOf(session) === 'doc' || !prompt.action?.trim();\n}\n\nif (!promptFitsSession(prompt, state)) {\n  prompt = { ...prompt, action: null }; // strip the action outside doc sessions\n}","typeGuard":"const isActionPrompt = (p: Partial<ChatPrompt>): boolean => !!p.action?.trim();","tryCatchPattern":"try {\n  await sessions.create(state);\n} catch (e) {\n  if (e instanceof CopilotPromptInvalid) {\n    // retry without the action, or inform the user action prompts need a doc session\n  }\n  throw e;\n}","preventionTips":["Derive the session type (pinned > no-docId > doc) before applying a prompt","Default action to null in shared prompt payloads and set it only for doc chats","Hide action prompts in workspace and pinned chat UIs"],"tags":["copilot","session","prompt","action","validation"],"backgroundTag":"prompt-not-allowed-for-session-type","analyzedSha":"2af30773aecd567f09b346e7b72fc69143144057","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}