toeverything/AFFiNE · error · CopilotPromptInvalid

copilot_prompt_invalid

copilot_prompt_invalid

Error message

${promptName} are not allowed for ${sessionType} sessions

What it means

CopilotPromptInvalid (invalid_input / copilot_prompt_invalid) thrown by CopilotSessionModel.checkSessionPrompt (packages/backend/server/src/models/copilot-session.ts:368). Sessions are classified Workspace / Pinned / Doc by getSessionType (pinned -> Pinned; no docId -> Workspace; else Doc). Workspace and Pinned sessions are not permitted to carry an action prompt — only Doc sessions are. If promptAction is a non-empty trimmed string for those session types, the guard throws.

Source

Thrown at packages/backend/server/src/models/copilot-session.ts:368

  getSessionType(session: Pick<ChatSession, 'docId' | 'pinned'>): SessionType {
    if (session.pinned) return SessionType.Pinned;
    if (!session.docId) return SessionType.Workspace;
    return SessionType.Doc;
  }

  checkSessionPrompt(
    session: Pick<ChatSession, 'docId' | 'pinned'>,
    prompt: Partial<ChatPrompt>
  ): boolean {
    const sessionType = this.getSessionType(session);
    const { name: promptName, action: promptAction } = prompt;

    // workspace and pinned sessions cannot use action prompts
    if (
      [SessionType.Workspace, SessionType.Pinned].includes(sessionType) &&
      !!promptAction?.trim()
    ) {
      throw new CopilotPromptInvalid(
        `${promptName} are not allowed for ${sessionType} sessions`
      );
    }

    return true;
  }

  @Transactional()
  async create(state: ChatSession, reuseChat = false): Promise<string> {
    // find and return existing session if session is chat session
    if (reuseChat && !state.promptAction) {
      const sessionId = await this.find(state);
      if (sessionId) return sessionId;
    }

    if (state.pinned) {
      await this.unpin(state.workspaceId, state.userId);
    }

View on GitHub (pinned to 26c515e050)

Solutions

  1. Target a Doc session: pass a valid docId and ensure pinned is false when sending an action prompt.
  2. Strip the action from the prompt before sending to a Workspace or Pinned session.
  3. On the client, gate the action UI on getSessionType(session) === 'doc' before submit.
  4. If pinning is required, refactor so the action runs against the underlying doc session, not the pinned wrapper.

Example fix

// before
sessionModel.checkSessionPrompt({ docId: null, pinned: false }, { name: 'summary', action: 'summarize' });
// after
sessionModel.checkSessionPrompt({ docId: doc.id, pinned: false }, { name: 'summary', action: 'summarize' });
Defensive patterns

Strategy: validation

Validate before calling

const sessionType = sessionModel.getSessionType({ docId, pinned });
const allowed = sessionType !== 'workspace' && sessionType !== 'pinned';
if (!allowed && prompt.action?.trim()) {
  throw new Error(`Action prompts not allowed for ${sessionType} sessions`);
}
sessionModel.checkSessionPrompt({ docId, pinned }, prompt);

Type guard

const allowsAction = (s: { docId: string | null; pinned: boolean }): boolean =>
  !s.pinned && !!s.docId;

Try / catch

try {
  sessionModel.checkSessionPrompt(session, prompt);
} catch (e) {
  if (e instanceof UserFriendlyError && e.code === 'copilot_prompt_invalid') {
    ui.warn('Actions require a doc session.');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Creating or reusing a Copilot session whose sessionType is Workspace or Pinned while supplying a ChatPrompt with a non-empty action. Concretely: state.pinned === true OR state.docId is empty, combined with prompt.action.trim() !== ''.

Common situations: Frontend reuses a workspace-level chat session object but attaches a doc-scoped action (e.g. 'summarize'); pinning a session that previously had an action; sending an action prompt against the workspace root rather than a doc.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/754e806f85b8b7b3. Report an issue: GitHub.