acoustic-carpenter-78188
04/15/2026, 1:43 PMValidateWorkflow in workflow_compiler.go iterates wf.Nodes (a Go map) in two places without sorting keys. Since Go map iteration is randomized, the same workflow produces different CompiledWorkflowClosure outputs across compilations, leading to different digests. FlyteAdmin then incorrectly takes the "different structure" code path instead of returning ALREADY_EXISTS.
Bug 2: Oversized error message (errors)
The "different structure" code path computes a jsondiff of two large compiled workflow closures and includes the full diff in the gRPC error description. For workflows with many dependencies (e.g. 400+ JARs), this diff exceeds gRPC's default 4MB MaxSendMsgSize. gRPC-Go silently rejects the response at the transport layer, sending RST_STREAM INTERNAL_ERROR to the client with no server-side log.
The client sees INTERNAL: RST_STREAM closed stream. HTTP/2 error code: INTERNAL_ERROR instead of any useful error.
What changes were proposed in this pull request?
Commit 1: Truncate diff in error messages (flyteadmin/pkg/errors/errors.go)
• Cap error messages at 3MB (leaving room for gRPC framing) in all three *ExistsDifferentStructureError functions
• When truncated, append ... [diff truncated — exceeded gRPC max message size]
• Safety net that prevents RST_STREAM regardless of diff size
Commit 2: Sort node IDs for deterministic compilation (flytepropeller/pkg/compiler/workflow_compiler.go)
• Sort wf.Nodes map keys before iterating in ValidateWorkflow
• Ensures identical workflows produce identical CompiledWorkflowClosure and therefore identical digests
• Root cause fix: re-registration of identical workflows correctly returns ALREADY_EXISTS
How did you test it?
• All existing tests pass: go test ./pkg/errors/ and go test ./pkg/compiler/...
• Reproduced the original issue on a production FlyteAdmin instance by calling CreateWorkflow with a modified template (same version) — confirmed RST_STREAM INTERNAL_ERROR
• Same call with identical template returns ALREADY_EXISTS (correct behavior)
• CreateTask always returns ALREADY_EXISTS (task digests are already deterministic — no map iteration in task compilation)
flyteorg/flyte