toeverything/AFFiNE · error · CopilotSessionInvalidInput

copilot_session_invalid_input

copilot_session_invalid_input

Error message

Cannot update action: ${session.id}

What it means

CopilotSessionInvalidInput thrown by update() when internalCall is false (the default) and the loaded session has a non-null promptAction - an 'action session' created to serve a prompt action. Action sessions are system-managed; the public update path refuses to modify them at all. The guard checks the stored session, not the payload, so even a harmless title or pin update is rejected.

Solutions

  1. Skip update() for action sessions - filter them out of editable lists in the UI/API layer
  2. If you are implementing trusted server logic that genuinely must modify the session, pass update(options, true)
  3. Create a new normal session instead of editing the action session

Example fix

// before
await sessions.update({ userId, sessionId, title }); // sessionId belongs to an action session

// after
const session = await sessions.getExists(sessionId, { promptAction: true }, { userId });
if (session?.promptAction) {
  // action sessions are system-managed; skip user-driven updates
} else {
  await sessions.update({ userId, sessionId, title });
}
Defensive patterns

Strategy: validation

Validate before calling

const session = await sessions.getExists(sessionId, { promptAction: true }, { userId });
if (session?.promptAction) {
  // action sessions are system-managed: skip user-driven updates
} else {
  await sessions.update({ userId, sessionId, title });
}

Type guard

type ActionSession = { promptAction: string };
const isActionSession = (
  s: { promptAction?: string | null }
): s is ActionSession => !!s.promptAction;

Try / catch

try {
  await sessions.update(options);
} catch (e) {
  if (e instanceof CopilotSessionInvalidInput && e.message.startsWith('Cannot update action')) {
    return; // system-managed session: ignore user edit
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling update(options) without passing internalCall = true on a session whose aiSession.promptAction column is set (the session was created from an action prompt).

Common situations: Chat UI listing action sessions next to normal chats and offering rename/pin; a generic 'update session' code path that runs against every session id the client holds.

Related errors


AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18). Data as JSON: /api/errors/1d5813ccbc254d69. Report an issue: GitHub.

Appendix: source

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

      sessionId,
      {
        id: true,
        workspaceId: true,
        docId: true,
        parentSessionId: true,
        pinned: true,
        promptAction: true,
      },
      { userId, workspaceId: options.workspaceId }
    );
    if (!session) {
      throw new CopilotSessionNotFound();
    }

    // not allow to update action session
    if (!internalCall) {
      if (session.promptAction) {
        throw new CopilotSessionInvalidInput(
          `Cannot update action: ${session.id}`
        );
      } else if (docId && session.parentSessionId) {
        throw new CopilotSessionInvalidInput(
          `Cannot update docId for forked session: ${session.id}`
        );
      }
    }

    let nextPromptAction: string | null | undefined;
    if (promptName) {
      nextPromptAction = options.promptAction;
      if (nextPromptAction === undefined) {
        throw new CopilotSessionInvalidInput(
          `Prompt action is required when changing prompt ${promptName}`
        );
      }
      if (nextPromptAction) {

View on GitHub (pinned to 2af30773ae)