{"record":{"id":"03312236b37f88b3","repo":"toeverything/AFFiNE","slug":"workspace-id-required-to-update-team-subscription","errorCode":"workspace_id_required_to_update_team_subscription","errorMessage":"Workspace id is required to update team subscription.","messagePattern":"Workspace id is required to update team subscription\\.","errorType":"exception","errorClass":"WorkspaceIdRequiredToUpdateTeamSubscription","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/plugins/payment/resolver.ts","lineNumber":344,"sourceCode":"      name: 'plan',\n      type: () => SubscriptionPlan,\n      nullable: true,\n      defaultValue: SubscriptionPlan.Pro,\n    })\n    plan: SubscriptionPlan,\n    @Args({ name: 'workspaceId', type: () => String, nullable: true })\n    workspaceId: string | null,\n    @Headers('idempotency-key') idempotencyKey?: string,\n    @Args('idempotencyKey', {\n      type: () => String,\n      nullable: true,\n      deprecationReason: 'use header `Idempotency-Key`',\n    })\n    _?: string\n  ) {\n    if (plan === SubscriptionPlan.Team) {\n      if (!workspaceId) {\n        throw new WorkspaceIdRequiredToUpdateTeamSubscription();\n      }\n\n      return this.service.cancelSubscription(\n        { workspaceId, plan },\n        idempotencyKey\n      );\n    }\n\n    return this.service.cancelSubscription(\n      {\n        userId: user.id,\n        // @ts-expect-error exam inside\n        plan,\n      },\n      idempotencyKey\n    );\n  }\n","sourceCodeStart":326,"sourceCodeEnd":362,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/payment/resolver.ts#L326-L362","documentation":"Thrown by the cancelSubscription GraphQL mutation when the requested plan is SubscriptionPlan.Team but the nullable workspaceId argument is null/omitted. Team subscriptions are scoped to a workspace, so the service cannot resolve which workspace's subscription to cancel without it. The resolver rejects the call before it reaches SubscriptionService.cancelSubscription.","triggerScenarios":"Calling cancelSubscription(plan: SubscriptionPlan.Team) (or the lowercase 'team' enum value) without the workspaceId argument; passing workspaceId: null; frontend sending an empty string that GraphQL coerces to null.","commonSituations":"UI has separate cancel flows for Pro (user-scoped, no workspace picker) and Team, and the Team branch forgets to thread the current workspace id; user's workspace context not yet loaded when the cancel button is clicked; a client built against the old API that never sent workspaceId.","solutions":["Pass the target workspace's id as the workspaceId argument whenever plan is 'team' in the cancelSubscription mutation.","In the client, derive workspaceId from the active workspace context (e.g. currentWorkspace.id) and disable the cancel button until a workspace is selected.","If you hit this from a script/tool, check the GraphQL operation document and add the $workspaceId variable to the variables payload."],"exampleFix":"# before\nmutation {\n  cancelSubscription(plan: team, recurring: monthly)\n}\n\n# after\nmutation Cancel($workspaceId: String!) {\n  cancelSubscription(plan: team, recurring: monthly, workspaceId: $workspaceId)\n}","handlingStrategy":"validation","validationCode":"const needsWorkspace = plan === 'team';\nif (needsWorkspace && !workspaceId) {\n  throw new Error('workspaceId is required to cancel a team subscription');\n}\nawait gql.cancelSubscription({ plan, recurring, workspaceId });","typeGuard":"function isTeamCancelArgs(a: { plan: string; workspaceId?: string | null }): a is { plan: 'team'; workspaceId: string } {\n  return a.plan === 'team' && typeof a.workspaceId === 'string' && a.workspaceId.length > 0;\n}","tryCatchPattern":"catch (e) { if (gqlErrorCode(e) === 'workspace_id_required_to_update_team_subscription') { promptWorkspaceSelection(); return; } throw e; }","preventionTips":["Build mutation variables through one helper that adds workspaceId for plan 'team'.","Disable plan actions in the UI until the workspace context is loaded.","Add a client unit test asserting team-plan cancel/resume/recurring mutations always include workspaceId."],"tags":["payment","subscription","workspace","graphql","validation"],"backgroundTag":"missing-required-argument","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"}