Bug
UserError values can include a public message, a user-defined code, and structured metadata. The message and metadata should be visible to the caller because UserError represents an intentional user-facing failure, not an internal runtime failure.
Example:
throw new UserError("quota exceeded", {
code: "quota_exceeded",
metadata: {
limit: 20,
attempted: 25,
},
});
Expected client-visible fields:
{
"group": "user",
"code": "quota_exceeded",
"message": "quota exceeded",
"metadata": {
"limit": 20,
"attempted": 25
}
}
Current behavior preserves the group and code, but masks the error at the rivetkit-core client boundary. The caller receives the generic internal-error message and no metadata, even though the error was explicitly constructed as a public UserError.
Cause
The TypeScript and NAPI bridge preserve the structured error correctly. The loss happens later in rivetkit-core when client-facing errors are filtered through client_error_message and client_error_metadata.
Those helpers only preserve message and metadata for groups recognized by public_error_status_code. The user group is missing from that allowlist, so core treats dynamic UserError values as private for message/metadata sanitization.
Impact
Clients can still see group == "user" and the user-defined code, but they lose the human-readable message and any structured metadata. That makes expected domain failures harder to present or handle precisely because details such as limits, attempted values, field names, or validation data are stripped before they reach the caller.
Scoped Fix
Treat group == "user" as public in rivetkit-rust/packages/rivetkit-core/src/error.rs so the core client boundary preserves the intended message and metadata. Add focused regression coverage for a dynamic user error with structured metadata.
Bug
UserErrorvalues can include a public message, a user-defined code, and structured metadata. The message and metadata should be visible to the caller becauseUserErrorrepresents an intentional user-facing failure, not an internal runtime failure.Example:
Expected client-visible fields:
{ "group": "user", "code": "quota_exceeded", "message": "quota exceeded", "metadata": { "limit": 20, "attempted": 25 } }Current behavior preserves the
groupandcode, but masks the error at therivetkit-coreclient boundary. The caller receives the generic internal-error message and no metadata, even though the error was explicitly constructed as a publicUserError.Cause
The TypeScript and NAPI bridge preserve the structured error correctly. The loss happens later in
rivetkit-corewhen client-facing errors are filtered throughclient_error_messageandclient_error_metadata.Those helpers only preserve message and metadata for groups recognized by
public_error_status_code. Theusergroup is missing from that allowlist, so core treats dynamicUserErrorvalues as private for message/metadata sanitization.Impact
Clients can still see
group == "user"and the user-defined code, but they lose the human-readable message and any structured metadata. That makes expected domain failures harder to present or handle precisely because details such as limits, attempted values, field names, or validation data are stripped before they reach the caller.Scoped Fix
Treat
group == "user"as public inrivetkit-rust/packages/rivetkit-core/src/error.rsso the core client boundary preserves the intended message and metadata. Add focused regression coverage for a dynamicusererror with structured metadata.