{"record":{"id":"e73549f00f594e5e","repo":"toeverything/AFFiNE","slug":"copilot-failed-to-add-workspace-artifact","errorCode":"copilot_failed_to_add_workspace_artifact","errorMessage":"Failed to add workspace artifact: ${message}","messagePattern":"Failed to add workspace artifact: (.+?)","errorType":"exception","errorClass":"CopilotFailedToAddWorkspaceArtifact","httpStatus":500,"severity":"error","filePath":"packages/backend/server/src/plugins/copilot/workspace/resolver.ts","lineNumber":190,"sourceCode":"    const lockFlag = `${COPILOT_LOCKER}:workspace:${workspaceId}`;\n    await using lock = await this.mutex.acquire(lockFlag);\n    if (!lock) {\n      throw new TooManyRequest('Server is busy');\n    }\n\n    const length = Number(ctx.req.headers['content-length']);\n    if (length && length >= MAX_EMBEDDABLE_SIZE) {\n      throw new BlobQuotaExceeded();\n    }\n\n    try {\n      return await this.copilotWorkspace.addArtifact(workspaceId, content);\n    } catch (e) {\n      // passthrough user friendly error\n      if (e instanceof UserFriendlyError) {\n        throw e;\n      }\n      throw new CopilotFailedToAddWorkspaceArtifact({\n        message: e instanceof Error ? e.message : String(e),\n      });\n    }\n  }\n\n  @Mutation(() => Boolean, {\n    name: 'removeWorkspaceArtifact',\n    complexity: 2,\n    description: 'Remove a workspace artifact',\n  })\n  async removeArtifact(\n    @CurrentUser() user: CurrentUser,\n    @Args('workspaceId', { type: () => String })\n    workspaceId: string,\n    @Args('artifactId', { type: () => String })\n    artifactId: string\n  ): Promise<boolean> {\n    await this.ac","sourceCodeStart":172,"sourceCodeEnd":208,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/copilot/workspace/resolver.ts#L172-L208","documentation":"Catch-all wrapper: any exception from copilotWorkspace.addArtifact that is not already a UserFriendlyError is rethrown as CopilotFailedToAddWorkspaceArtifact (internal_server_error), embedding the original message. The original cause is only visible in the wrapped message and server logs.","triggerScenarios":"Embedding provider network/API failure mid-processing, database write errors while persisting the artifact, or file stream/read failures inside addArtifact.","commonSituations":"Embedding API key rotated after the health check passed; transient DB outage; unsupported MIME type hitting an unguarded code path; content-length missing so the size check was skipped and processing choked on an oversized stream.","solutions":["Read the embedded message and the server logs to identify the underlying exception before the wrap","Verify embedding provider connectivity/keys and database health, then retry the upload","If the same input always fails, inspect the artifact (format/size) - deterministic failures indicate an unsupported input rather than a transient fault"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"const isWorkspaceArtifactFailure = (e: unknown): boolean =>\n  Boolean(e && typeof e === 'object' && (e as any).code === 'copilot_failed_to_add_workspace_artifact');","tryCatchPattern":"try {\n  return await mutate({ workspaceId, blob });\n} catch (e) {\n  if (e?.code === 'copilot_failed_to_add_workspace_artifact') {\n    report(e.message); // wrapped cause: embedding provider / DB / stream error\n    return null; // or surface a retry to the user\n  }\n  throw e;\n}","preventionTips":["Monitor the wrapped message - it carries the underlying cause and is your only client-side clue","Keep embedding provider credentials valid; rotate keys before expiry","Test one small artifact after any infra change to catch systemic causes early"],"tags":["copilot","artifact","wrapper","internal-error","upload"],"backgroundTag":"upload-processing-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"}