Values returned by Pulumi resources are Output wrappers, not plain values, because they are not known until the resource is created. Placing one in a template literal produces the string form of the wrapper, typically Calling [toString] on an [Output] is not supported in TypeScript or an object repr in Python, rather than the value.
The fix is to stay inside the Output world. Use pulumi.interpolate with a tagged template, or pulumi.all([a, b]).apply(([a, b]) => ...) when several outputs are needed, or the equivalent Output.concat and Output.all in Python. Anything computed inside apply is itself an Output.
A related trap: code inside apply does not run during a preview, because the input value is unknown then. Creating a resource inside apply therefore hides it from pulumi preview, and Pulumi warns about this explicitly. Structure programs so resource creation happens at the top level and only value transformation happens inside apply. During preview, an unknown Output reads as undefined in TypeScript, so guards written against it behave differently in preview and update.