Skip to main content
Every item here is current behaviour rather than a bug report. Several are the places where contributions would land best.

Not implemented

The Server type has OnInvoke but no Invoke; the Client type has Invoke but no OnInvoke. The server’s dispatcher only handles requests and the client’s only handles responses, so round trips run from client to server only.The workaround is two events. See Invocations, which is also the safer design, since a server thread parked on a client’s response is a thread an exploiter can hold open.
Flux.Server and Flux.Client each error in the wrong context, and there is no auto-detecting factory. The internal Bridge module is the batching layer, not an environment selector, and it is not re-exported.The workaround is to branch on RunService:IsServer(). See Shared Modules.
Listeners live in module-level tables keyed by event name. Nothing observes script lifetime, Destroying or AncestryChanged. A listener registered by a destroyed LocalScript stays registered for the session, keeps firing, and keeps its upvalues alive.The workaround is to disconnect explicitly. See Connections.
WriteAny recurses into tables with no visited-set and no depth limit, so a self-referencing table overflows the Luau stack.The workaround is to break cycles, or send an id and rebuild on the receiver.
There is no interception point between the wire and your listener, so every payload reaches your callback exactly as decoded.The workaround is to validate at the top of each server listener, and rate-limit per player yourself.

Fixed constants

None of these are configurable through the public API.

Silent failure modes

These are the ones most likely to cost you an afternoon.
Unsupported datatypes become nil. One warn on the sender, nothing on the receiver, and the argument count still matches, so your callback runs with a hole in its arguments. Check Supported Types.
Schema mismatches corrupt silently. Flux never negotiates or validates layouts. The sender writes what its schema says and the receiver reads what its schema says, so a mismatch desynchronises the cursor and every subsequent value in the packet is garbage. Define schemas in one shared module.
Integer overflow truncates without warning. Schema number types pass straight to buffer.write*, so T.UInt8() given 300 transmits 44. Clamp anything whose range you do not control.
Unseen instances arrive as nil. ReadInstance indexes the side array, and if Roblox declined to replicate the reference the result is nil. The declared type is Type<Instance>, so the type checker will not prompt you to check. Nil-check anyway.
maxLen is write-side only. T.String(20) and T.Array(t, 50) assert when sending and raise at your call site. Read ignores the bound entirely, so a tampered client can send any length. Re-validate inbound data in server listeners.
A missing OnInvoke handler drops requests. There is no error on either side; the client simply yields for the full 60 seconds. Register handlers during startup.

Ordering and timing

Batching adds up to one frame, roughly 16 ms at 60 FPS, between Fire returning and the bytes leaving. Flux is not suitable when a message has to be on the wire immediately, and a Fire followed by destroying the referenced instance may flush after it is gone.

Name collisions

All state is keyed by event name in module-level tables, so two systems that pick the same name silently share traffic, and OnInvoke assigned twice on one name replaces rather than adds.
Namespace names in a large place: "Combat.Hit", "Shop.Purchase", "UI.Toast". Names are also written into every packet, so keep hot ones short. At high frequency the channel name can dominate the payload.

Payload size

large payloads split by the engine
Flux_Reliable follows normal RemoteEvent behaviour. Very large batches cost bandwidth and decode time but are not dropped.
hard engine limit
UnreliableRemoteEvent caps its payload. Because Flux batches, the cap applies to the whole accumulated frame rather than to your individual call, so many unreliable sends in one frame can collectively exceed it even when each is small.
There is no size check before flushing, so an oversized unreliable batch fails at the engine level rather than anywhere in Flux. Keep unreliable traffic to a small, bounded number of sends per frame.

Security

Neither generics nor schemas are runtime validation.Generics are erased at runtime. Schemas constrain the decoder’s expectations, not the values, so a tampered client can send any bytes and T.UInt8() will happily decode whatever byte arrives as a number in 0 to 255.Every server listener and OnInvoke handler has to validate as if the input came from a raw RemoteEvent: check types, clamp ranges, verify ownership, and rate-limit per player.

Studio and environment

required
Flux’s internal modules require each other by relative path, as in @self/Utils/Types and ./Utils/Buffers. This has to be available in your environment.
required
The client resolves remotes with WaitForChild, so if no server code calls Flux.Server(...), every client hangs indefinitely. Requiring the root module is not enough, because the transport initialises lazily on the first constructor call.

Next steps

Migrating

Coming from raw remotes or a Warp-style API.

Supported types

What survives the wire.