## Failure
Workflow SDK `5.0.0-beta.40` produces an invalid workflow bundle when a
Chat SDK serializable class such as `Message`, `ThreadImpl`, or
`ChannelImpl` crosses a workflow step boundary.
The Workflow compiler imports the emitted module containing each class
to register its `@workflow/serde` methods. In Chat SDK `4.37.0`, tsup
emits those classes in `dist/index.js`. The root entry also imports the
conversation-scoping implementation added in #751, which uses
`AsyncLocalStorage` from `node:async_hooks`. Serializer registration
therefore pulls Node-only code into the sandboxed workflow bundle before
any workflow or step executes.
Build warning:
```text
Serde warning for classes "ChannelImpl", "Message", "ThreadImpl":
Workflow bundle contains Node.js built-in imports: async_hooks.
These will fail at runtime in the workflow sandbox.
```
Deployed workflows then fail during module initialization:
```text
var import_async_hooks = require("async_hooks");
^
ReferenceError: require is not defined
```
## Minimal reproduction
```json
{
"dependencies": {
"chat": "4.37.0",
"workflow": "5.0.0-beta.40"
}
}
```
```ts
import { Message } from "chat";
async function createMessageStep(value: string): Promise<Message> {
"use step";
return new Message({
id: "message",
threadId: "slack:C123:123.456",
text: value,
formatted: {
type: "root",
children: [
{
type: "paragraph",
children: [{ type: "text", value }],
},
],
},
raw: {},
author: {
userId: "U123",
userName: "user",
fullName: "User",
isBot: false,
isMe: false,
},
metadata: { dateSent: new Date(), edited: false },
attachments: [],
});
}
export async function testWorkflow(value: string): Promise<string> {
"use workflow";
const message = await createMessageStep(value);
return message.text;
}
```
Running `workflow build` on `4.37.0` emits the warning; deploying the
output produces the runtime failure above.
## Fix
- Add a dedicated `chat/serialization` package entry for `Message`,
`ThreadImpl`, `ChannelImpl`, `reviver`, and their serialized DTO types.
- Make serializer code a second tsup entry and explicitly enable
splitting. The serializer-bearing classes are now emitted into a shared
chunk with no dependency on `Chat` or its Node-only conversation
context.
- Preserve the existing root exports and automatic `@workflow/serde`
behavior. Existing `import { Message } from "chat"` workflow code
remains valid.
- Add a post-build module-graph assertion that fails if any emitted
serializer registration can transitively import a Node.js builtin.
- Test against Workflow SDK `5.0.0-beta.40`, the compiler version that
exposed the invalid bundle.
- Add a minor changeset for the fixed-version Chat SDK packages,
producing the `4.38.0` release line.
After the change, the emitted serializer classes live in a sandbox-safe
shared chunk while `AsyncLocalStorage` remains in a separate Node
runtime chunk. The exact reproduction compiles successfully with `5
steps, 1 workflow` and no Serde warning.
## Control cases
The failure requires a serializable Chat class to cross a durable
boundary. These cases were already safe and remain unchanged:
- `AsyncLocalStorage` used entirely inside a `"use step"` function.
- A Chat `Message` created and consumed within one step while returning
plain data.
- Request handlers that convert Chat objects to plain workflow DTOs
before starting a workflow.
- `@vercel/sandbox` used entirely inside a step.
## Validation
- Committed beta.40 reproduction fixture: type-correct and compiled
during every Chat package build with no Node builtin / Serde warning.
- Emitted serializer module graph: no transitive Node.js builtins.
- Chat package: 1,113 tests pass.
- Chat package typecheck passes.
- Repository formatting and lint checks pass.
- Package build passes.
Full repository validation reaches the pre-existing `knip` baseline and
reports unrelated unused dependencies and unlisted binaries in examples
and adapter packages.
---------
Signed-off-by: bryan-hunter <bryan.hunter@vercel.com>