{"record":{"id":"1d5813ccbc254d69","repo":"toeverything/AFFiNE","slug":"copilot-session-invalid-input","errorCode":"copilot_session_invalid_input","errorMessage":"Cannot update action: ${session.id}","messagePattern":"Cannot update action: (.+?)","errorType":"exception","errorClass":"CopilotSessionInvalidInput","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/models/copilot-session.ts","lineNumber":724,"sourceCode":"      sessionId,\n      {\n        id: true,\n        workspaceId: true,\n        docId: true,\n        parentSessionId: true,\n        pinned: true,\n        promptAction: true,\n      },\n      { userId, workspaceId: options.workspaceId }\n    );\n    if (!session) {\n      throw new CopilotSessionNotFound();\n    }\n\n    // not allow to update action session\n    if (!internalCall) {\n      if (session.promptAction) {\n        throw new CopilotSessionInvalidInput(\n          `Cannot update action: ${session.id}`\n        );\n      } else if (docId && session.parentSessionId) {\n        throw new CopilotSessionInvalidInput(\n          `Cannot update docId for forked session: ${session.id}`\n        );\n      }\n    }\n\n    let nextPromptAction: string | null | undefined;\n    if (promptName) {\n      nextPromptAction = options.promptAction;\n      if (nextPromptAction === undefined) {\n        throw new CopilotSessionInvalidInput(\n          `Prompt action is required when changing prompt ${promptName}`\n        );\n      }\n      if (nextPromptAction) {","sourceCodeStart":706,"sourceCodeEnd":742,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/models/copilot-session.ts#L706-L742","documentation":"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.","triggerScenarios":"Calling update(options) without passing internalCall = true on a session whose aiSession.promptAction column is set (the session was created from an action prompt).","commonSituations":"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.","solutions":["Skip update() for action sessions - filter them out of editable lists in the UI/API layer","If you are implementing trusted server logic that genuinely must modify the session, pass update(options, true)","Create a new normal session instead of editing the action session"],"exampleFix":"// before\nawait sessions.update({ userId, sessionId, title }); // sessionId belongs to an action session\n\n// after\nconst session = await sessions.getExists(sessionId, { promptAction: true }, { userId });\nif (session?.promptAction) {\n  // action sessions are system-managed; skip user-driven updates\n} else {\n  await sessions.update({ userId, sessionId, title });\n}","handlingStrategy":"validation","validationCode":"const session = await sessions.getExists(sessionId, { promptAction: true }, { userId });\nif (session?.promptAction) {\n  // action sessions are system-managed: skip user-driven updates\n} else {\n  await sessions.update({ userId, sessionId, title });\n}","typeGuard":"type ActionSession = { promptAction: string };\nconst isActionSession = (\n  s: { promptAction?: string | null }\n): s is ActionSession => !!s.promptAction;","tryCatchPattern":"try {\n  await sessions.update(options);\n} catch (e) {\n  if (e instanceof CopilotSessionInvalidInput && e.message.startsWith('Cannot update action')) {\n    return; // system-managed session: ignore user edit\n  }\n  throw e;\n}","preventionTips":["Filter action sessions out of editable chat lists in the UI","Reserve internalCall = true for trusted server code paths only","Expose promptAction on the session DTO so clients can branch"],"tags":["copilot","session","action","read-only","validation"],"backgroundTag":"immutable-record-update","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"}