{"record":{"id":"6d6ef10e1895e894","repo":"Dokploy/dokploy","slug":"bad-request-6d6ef1","errorCode":"BAD_REQUEST","errorMessage":"externalGRPCPort cannot be set when sqldNode is 'replica'","messagePattern":"externalGRPCPort cannot be set when sqldNode is 'replica'","errorType":"exception","errorClass":"TRPCError","httpStatus":400,"severity":"error","filePath":"apps/dokploy/server/api/routers/libsql.ts","lineNumber":184,"sourceCode":"\n\t\t\tawait audit(ctx, {\n\t\t\t\taction: \"stop\",\n\t\t\t\tresourceType: \"service\",\n\t\t\t\tresourceId: libsql.libsqlId,\n\t\t\t\tresourceName: libsql.appName,\n\t\t\t});\n\t\t\treturn libsql;\n\t\t}),\n\tsaveExternalPorts: protectedProcedure\n\t\t.input(apiSaveExternalPortsLibsql)\n\t\t.mutation(async ({ input, ctx }) => {\n\t\t\tawait checkServicePermissionAndAccess(ctx, input.libsqlId, {\n\t\t\t\tservice: [\"create\"],\n\t\t\t});\n\t\t\tconst libsql = await findLibsqlById(input.libsqlId);\n\n\t\t\tif (libsql.sqldNode === \"replica\" && input.externalGRPCPort !== null) {\n\t\t\t\tthrow new TRPCError({\n\t\t\t\t\tcode: \"BAD_REQUEST\",\n\t\t\t\t\tmessage: \"externalGRPCPort cannot be set when sqldNode is 'replica'\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst portsToCheck = [\n\t\t\t\t{\n\t\t\t\t\tport: input.externalPort,\n\t\t\t\t\tname: \"externalPort\",\n\t\t\t\t\tcurrent: libsql.externalPort,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tport: input.externalGRPCPort,\n\t\t\t\t\tname: \"externalGRPCPort\",\n\t\t\t\t\tcurrent: libsql.externalGRPCPort,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tport: input.externalAdminPort,","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/Dokploy/dokploy/blob/546686ea3587f12ec5652217dedd9f7960fb6d15/apps/dokploy/server/api/routers/libsql.ts#L166-L202","documentation":"Libsql replicas sync from a primary and must not expose their own external gRPC port. The update procedure rejects the combination sqldNode === 'replica' with a non-null externalGRPCPort as BAD_REQUEST — a domain business rule, not a system failure.","triggerScenarios":"Editing a database and switching its role to 'replica' while leaving an externalGRPCPort set, or explicitly sending a port for a replica.","commonSituations":"Migrating a standalone Libsql into a replica topology without clearing the previously assigned gRPC port in the same update.","solutions":["Set externalGRPCPort to null (or omit it) when choosing sqldNode 'replica'","If you need the port, keep/promote the node to primary instead","Split into two updates only if the UI requires: first clear the port, then change role"],"exampleFix":"// before\nawait api.libsql.update.mutate({ libsqlId, sqldNode: \"replica\", externalGRPCPort: 5001 });\n// after\nawait api.libsql.update.mutate({ libsqlId, sqldNode: \"replica\", externalGRPCPort: null });","handlingStrategy":"validation","validationCode":"if (sqldNode === \"replica\") input.externalGRPCPort = null;","typeGuard":"const isValidRolePortCombo = (node: string, port: number | null): boolean =>\n  node !== \"replica\" || port === null;","tryCatchPattern":null,"preventionTips":["UI should clear/lock the gRPC port field when replica is chosen"],"tags":["libsql","business-rule","replica","validation","trpc"],"backgroundTag":"invalid-request-combination","analyzedSha":"546686ea3587f12ec5652217dedd9f7960fb6d15","analyzedAt":"2026-08-27T05:18:58.095Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}