> ## Documentation Index
> Fetch the complete documentation index at: https://flux-docs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Known Limits

> Constraints, sharp edges and behaviour worth knowing before you ship.

Every item here is current behaviour rather than a bug report. Several are the places where
[contributions](/#contributing-and-feedback) would land best.

## Not implemented

<AccordionGroup>
  <Accordion title="No server-to-client invocation" icon="ban">
    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](/guides/invocations#direction), which is also
    the safer design, since a server thread parked on a client's response is a thread an exploiter
    can hold open.
  </Accordion>

  <Accordion title="No unified constructor" icon="ban">
    `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](/guides/shared-modules).
  </Accordion>

  <Accordion title="No automatic connection cleanup" icon="ban">
    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](/guides/connections#flux-does-not-clean-up-after-you).
  </Accordion>

  <Accordion title="No circular table support" icon="ban">
    `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.
  </Accordion>

  <Accordion title="No middleware, rate limiting or validation hooks" icon="ban">
    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.
  </Accordion>
</AccordionGroup>

## Fixed constants

None of these are configurable through the public API.

| Constant          | Value  | Effect                                       |
| ----------------- | ------ | -------------------------------------------- |
| `REQUEST_TIMEOUT` | 60 s   | Invoke deadline before `"Request timed out"` |
| `FRAME_TIME`      | 1/60 s | Minimum interval between flushes             |
| `ALLOC_SIZE`      | 4096 B | Initial writer capacity                      |
| `POOL_SIZE`       | 512    | Maximum pooled writers and readers           |
| `MAX_REUSE_SIZE`  | 64 KB  | Writers above this are discarded, not pooled |
| Enum schema index | `u8`   | `T.Enum()` breaks past 256 items             |
| Packet type field | 2 bits | Room for one more packet type                |

## Silent failure modes

These are the ones most likely to cost you an afternoon.

<Warning>
  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](/reference/supported-types#not-supported).
</Warning>

<Warning>
  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.
</Warning>

<Warning>
  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.
</Warning>

<Warning>
  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.
</Warning>

<Warning>
  `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.
</Warning>

<Warning>
  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.
</Warning>

## Ordering and timing

| Guarantee                                             | Holds?                                                                                                                                                                  |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Two reliable sends to the same recipient              | <Tooltip tip="Guaranteed: you can rely on this ordering."><Icon icon="check" color="#16a34a" /></Tooltip> ordered                                                       |
| Reliable across different event names, same recipient | <Tooltip tip="Guaranteed: you can rely on this ordering."><Icon icon="check" color="#16a34a" /></Tooltip> ordered                                                       |
| Reliable against unreliable                           | <Tooltip tip="Not guaranteed: never write code that depends on this ordering."><Icon icon="xmark" color="#dc2626" /></Tooltip> separate remotes                         |
| `FireAll` against `Fire`                              | <Tooltip tip="Not guaranteed: never write code that depends on this ordering."><Icon icon="xmark" color="#dc2626" /></Tooltip> separate buffers, broadcasts flush first |
| Invoke responses against request order                | <Tooltip tip="Not guaranteed: never write code that depends on this ordering."><Icon icon="xmark" color="#dc2626" /></Tooltip> handlers are spawned concurrently        |
| Listener execution order                              | <Tooltip tip="Not guaranteed: never write code that depends on this ordering."><Icon icon="xmark" color="#dc2626" /></Tooltip> `task.spawn`, list walked backwards      |

<Warning>
  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.
</Warning>

## 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.

<Tip>
  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.
</Tip>

## Payload size

<ResponseField name="Reliable" type="large payloads split by the engine">
  `Flux_Reliable` follows normal `RemoteEvent` behaviour. Very large batches cost bandwidth and
  decode time but are not dropped.
</ResponseField>

<ResponseField name="Unreliable" type="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.
</ResponseField>

<Warning>
  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.
</Warning>

## Security

<Warning>
  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.
</Warning>

## Studio and environment

<ResponseField name="Require-by-string" type="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.
</ResponseField>

<ResponseField name="Server must initialise first" type="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.
</ResponseField>

## Next steps

<CardGroup cols={2}>
  <Card title="Migrating" icon="right-left" href="/reference/migrating">
    Coming from raw remotes or a Warp-style API.
  </Card>

  <Card title="Supported types" icon="table-list" href="/reference/supported-types">
    What survives the wire.
  </Card>
</CardGroup>
