{"record":{"id":"d496c22f9d465e96","repo":"toeverything/AFFiNE","slug":"32001","errorCode":"-32001","errorMessage":"Title cannot be empty","messagePattern":"Title cannot be empty","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/backend/server/src/plugins/copilot/mcp/provider.ts","lineNumber":244,"sourceCode":"              type: 'string',\n              description: 'The markdown content for the document body',\n            },\n          },\n          required: ['title', 'content'],\n          additionalProperties: false,\n        },\n        execute: async ({ title, content }, options) => {\n          try {\n            await this.ac\n              .user(userId)\n              .workspace(workspaceId)\n              .assert('Workspace.CreateDoc');\n\n            const abortedAfterPermission = abortIfNeeded(options.signal);\n            if (abortedAfterPermission) return abortedAfterPermission;\n\n            const sanitizedTitle = title.replace(/[\\r\\n]+/g, ' ').trim();\n            if (!sanitizedTitle) throw new Error('Title cannot be empty');\n            const strippedContent = content.replace(\n              /^[ \\t]{0,3}#\\s+[^\\n]*#*\\s*\\n*/,\n              ''\n            );\n            const result = await this.writer.createDoc(\n              workspaceId,\n              sanitizedTitle,\n              strippedContent,\n              userId\n            );\n\n            return toolText(\n              JSON.stringify({\n                success: true,\n                docId: result.docId,\n                message: `Document \"${title}\" created successfully`,\n              })\n            );","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/copilot/mcp/provider.ts#L226-L262","documentation":"Error('Title cannot be empty') thrown by the MCP doc-creation tool after it collapses all \\r\\n runs to spaces and trims: if nothing remains, the title is unusable. The MCP controller catches tool execution errors and returns them as JSON-RPC code -32001 'Error executing tool: Title cannot be empty'. It fires after the Workspace.CreateDoc permission check, so authorized callers with junk titles still hit it.","triggerScenarios":"Calling the create-doc MCP tool with a title of only newlines/spaces; a title that is empty after stripping markdown heading noise; an LLM passing a whitespace-only title argument.","commonSituations":"Agent derives the title from a document whose first line is blank; title string built from a template with empty interpolation; prompt-injection or parsing bug yields '\\n\\n'.","solutions":["Validate/trim the title client- (or agent-)side and require at least one non-whitespace character","Fall back to a generated default title (e.g. 'Untitled' or a date-based name) when the derived title is blank","Sanitize earlier: strip markdown '#' prefixes before sending, since the tool also strips them from content"],"exampleFix":"// before\ntools.call('create_doc', { title: '\\n  \\n', content });\n\n// after\nconst title = rawTitle.replace(/[\\r\\n]+/g, ' ').trim() || 'Untitled';\ntools.call('create_doc', { title, content });","handlingStrategy":"validation","validationCode":"const sanitizedTitle = title.replace(/[\\r\\n]+/g, ' ').trim();\nif (!sanitizedTitle) throw new Error('Derived title is empty — supply a default');\nawait tools.call('create_doc', { title: sanitizedTitle, content });","typeGuard":"const isNonEmptyTitle = (t: unknown): t is string =>\n  typeof t === 'string' && t.replace(/[\\r\\n]+/g, ' ').trim().length > 0;","tryCatchPattern":"try {\n  return await tools.call('create_doc', { title, content });\n} catch (e) {\n  if (e.code === -32001 && /Title cannot be empty/.test(e.message)) {\n    return tools.call('create_doc', { title: 'Untitled', content });\n  }\n  throw e;\n}","preventionTips":["Always sanitize+trim agent-derived titles and fall back to a default","Add minLength/trim rules to the tool's inputSchema so bad titles fail as params errors","Log the source of empty titles to find the upstream extraction bug"],"tags":["mcp","tool","doc","title","validation"],"backgroundTag":"input-validation-failed","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}