Skip to content

Date objects in forwarded tail events arrive as strings in local dev #15180

Description

@winklemad

What versions & operating system are you using?

main (fb6b51b), Node v22.17.1, macOS 15. Reproduced against packages/miniflare in the monorepo; the core of it reproduces in plain Node with no Workers involved.

Please provide a link to a minimal reproduction

Inlined below — it's six lines of plain JS against the replacer as it's currently written, so a linked repo would only add noise.

const SERIALIZED_DATE = "___serialized_date___";
const replacer = (_, value) =>
	value instanceof Date ? { [SERIALIZED_DATE]: value.toISOString() } : value;

console.log(JSON.stringify({ scheduledTime: new Date(0) }, replacer));
// {"scheduledTime":"1970-01-01T00:00:00.000Z"}   <- no tag, so nothing to revive

Describe the Bug

When tail events are forwarded between two local dev sessions through the dev registry, Date values arrive at the tail consumer as ISO strings rather than Date objects. event.scheduledTime.getTime() throws in local dev, while the same tail handler works in production. TraceItemAlarmEventInfo.scheduledTime is typed Date, so the types promise something local dev doesn't deliver.

The forwarding path serialises with JSON.stringify(events, tailEventsReplacer) and revives with tailEventsReviver (packages/miniflare/src/workers/core/dev-registry-proxy-shared.worker.ts). The replacer is meant to tag Dates so the reviver can rebuild them:

export function tailEventsReplacer(_: string, value: any) {
	if (value instanceof Date) {
		return { [SERIALIZED_DATE]: value.toISOString() };
	} else if (typeof value === "bigint") {
		return { [SERIALIZED_BIGINT]: value.toString() };
	}
	return value;
}

JSON.stringify() applies Date.prototype.toJSON() before it calls the replacer, so a real Date reaches the replacer already flattened to an ISO string. value instanceof Date is therefore never true for it, no tag is written, and the reviver has nothing to restore. The bigint arm is unaffected, which is why that half of the round-trip works.

Worth noting the instanceof check isn't dead: a value whose own toJSON() returns a Date reaches the replacer unconverted and is tagged correctly today, so that behaviour should be preserved rather than swapped out.

packages/vite-plugin-cloudflare/src/workers/vite-proxy-worker/index.ts carries its own copy of the pair with the same Date behaviour. The two aren't identical — the vite copy has no bigint handling — so they're the same Date issue in two places rather than one shared implementation.

This is a local-dev fidelity problem (dev diverging from production), not a production issue.

Please provide any relevant error logs

AssertionError: expected '2025-05-01T12:34:56.000Z' to be an instance of Date

I have a fix with tests and will open a PR shortly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions