What version of Effect is running?
effect@4.0.0-beta.57 (also on main)
What steps can reproduce the bug?
Problem
ClusterWorkflowEngine builds two entities per workflow with the same type Workflow/${name}: the full one has the run RPC, the partial one doesn't. Entity Equal/Hash are by type only, so they collide in Sharding's client ResourceMap — whichever client is built first wins for idleTimeToLive (5 min).
If the partial client is built first (any DurableDeferred.succeed/resume/activity), a later Workflow.execute(...) reuses it and throws, because the partial client's proxy has no run:
TypeError: make(executionId).run is not a function
Reproduction
import { Effect, Layer, Schema } from "effect"
import { ClusterWorkflowEngine, TestRunner } from "effect/unstable/cluster"
import * as WorkflowEngine from "effect/unstable/workflow/WorkflowEngine"
import { DurableDeferred, Workflow } from "effect/unstable/workflow"
const wf = Workflow.make({
name: "demo/run",
payload: Schema.Struct({ runId: Schema.String }),
success: Schema.Struct({ runId: Schema.String }),
error: Schema.Never,
idempotencyKey: ({ runId }) => runId,
})
const engineLayer = ClusterWorkflowEngine.layer.pipe(Layer.provide(TestRunner.layer))
const scenario = (deferredFirst: boolean) =>
Effect.gen(function* () {
const engine = yield* WorkflowEngine.WorkflowEngine
if (deferredFirst) {
const d = DurableDeferred.make("d", { success: Schema.Struct({ status: Schema.String }) })
yield* Effect.gen(function* () {
const token = yield* DurableDeferred.tokenFromPayload(d, { workflow: wf, payload: { runId: "r1" } })
yield* DurableDeferred.succeed(d, { token, value: { status: "ok" } })
}).pipe(Effect.provideService(WorkflowEngine.WorkflowEngine, engine), Effect.ignore)
}
return yield* wf.execute({ runId: "r2" }, { discard: true }).pipe(
Effect.provideService(WorkflowEngine.WorkflowEngine, engine),
Effect.matchCause({ onSuccess: () => "OK", onFailure: (c) => "FAIL: " + String(c).split("\n")[0] }),
)
}).pipe(Effect.provide(engineLayer), Effect.scoped)
Effect.runPromise(Effect.gen(function* () {
console.log("execute first ->", yield* scenario(false))
console.log("deferred then exec ->", yield* scenario(true))
}) as any)
Output:
execute first -> OK
deferred then exec -> FAIL: Die(TypeError: make(executionId).run is not a function ...)
The only difference is call order.
What is the expected behavior?
Workflow.execute should succeed regardless of whether a partial-entity op ran first — the full and partial entities should not share a client-cache slot.
What do you see instead?
make(executionId).run is not a function
Additional information
Possible cause (packages/cluster/src)
// Entity.ts — equality/hash by type only
[Hash.symbol]() { return Hash.structure({ type: this.type }) }
[Equal.symbol](that) { return isEntity(that) && this.type === that.type }
// ClusterWorkflowEngine.ts — both entities share `Workflow/${name}`
makeWorkflowEntity -> Entity.make(`Workflow/${workflow.name}`, [run, deferred, resume, activity])
makePartialWorkflowEntity -> Entity.make(`Workflow/${workflowName}`, [deferred, resume, activity]) // no run
Sharding.makeClient reads clients: ResourceMap keyed by entity equality, so get(fullEntity) returns the cached partial client (no run).
What version of Effect is running?
effect@4.0.0-beta.57 (also on
main)What steps can reproduce the bug?
Problem
ClusterWorkflowEnginebuilds two entities per workflow with the same typeWorkflow/${name}: the full one has therunRPC, the partial one doesn't.EntityEqual/Hashare bytypeonly, so they collide in Sharding's clientResourceMap— whichever client is built first wins foridleTimeToLive(5 min).If the partial client is built first (any
DurableDeferred.succeed/resume/activity), a laterWorkflow.execute(...)reuses it and throws, because the partial client's proxy has norun:Reproduction
Output:
The only difference is call order.
What is the expected behavior?
Workflow.executeshould succeed regardless of whether a partial-entity op ran first — the full and partial entities should not share a client-cache slot.What do you see instead?
make(executionId).run is not a function
Additional information
Possible cause (
packages/cluster/src)Sharding.makeClientreadsclients: ResourceMapkeyed by entity equality, soget(fullEntity)returns the cached partial client (norun).