# unreadable.gg changelog

Every release of the decompiler, newest first. Each is named by its [version](https://unreadable.gg/docs/api#version), the one the API sends with every result, so you can look up what changed in the version that decompiled your file.

## 2026.11.09-62f1c30 · fewer placeholders, plainer markers and new readability switches

- Far fewer functions come back as placeholders. Code where both arms of an `if` end in the same place, `continue` in a `repeat` with an `until` test, `pcall(fn, ...)` inside a loop that returns, and returns from deep inside loops now decompile.
- Fixed: a `repeat` returning a power could print `-0` where the original printed `0`.
- Fixed: `return x or (if a then b else c)` inside a `repeat` could read a name that was never defined.
- Fixed: an `elseif` could test `false` because a temporary reused a parameter's slot.
- Fixed: some locals and captured values were read as globals, in `repeat ... until` tests, unused functions and closures.
- Fixed: in functions with more than 255 constants, some divisions and `or`s used the wrong constant.
- Fixed: some closures the original shared were created twice, and `inline-calls` could inline a helper that was only set on one path, calling `nil`.
- Markers are shorter and plainer: one banner at the top, a `fix:` line where there's a fix, and no shouting.
- New switch `const-locals`: a local that's never assigned again prints as `const` (Luau 0.727 or later parses it).
- New switch `field-functions`: a table of keyed functions prints as `local t = {}` and `function t.k()`.
- New switch `line-notes`: under the readable layout, each function's first line ends in `-- line N`, where it was defined.
- New switch `mutable-globals`: `game.Players` and `workspace.CurrentCamera` print without the parentheses.
- `for _, v in keys, nil, nil do` prints as `for _, v in keys do`.
- Lines match the original more often, including in files with no final newline.
- More `.rbxl`/`.rbxm` files open: scripts whose bytecode is stored as text, and `.rbxlx`/`.rbxmx` files with a stray `]]>` in a script, which used to be refused whole.
- `modelNames` now names large scripts too.
- Memberships come in three tiers, Pro, Max and Ultra, each with its own daily decompile time. Members get up to 300 seconds a decompile, and a key works from 2 networks at once and 5 a day.
- The [Discord bot](https://unreadable.gg/docs#discord) decompiles with `/decompile`, with the same free limit, and `/status` shows how many you have left.
- The [account page](https://unreadable.gg/account) shows today's usage as a bar. Memberships are bought in our Discord server.
- The docs are shorter and plainer.

## 2026.10.29-c2107ad · xml place files

- You can now drop `.rbxlx` and `.rbxmx` files on the site, just like `.rbxl` and `.rbxm`.
- Fewer functions are left as stubs: many scripts that had a refused function now decompile in full.
- Optimised scripts decompile more often, especially ones that call small helper functions.
- Loops that always return, or can never run, no longer stop the code around them from decompiling.
- Fewer spots are marked as uncertain.

## 2026.10.29-d0af3ea · more scripts decompile, and new tools for agents

- More code now decompiles fully instead of being left as a placeholder. This includes `if`/`else` expressions, tables built from function calls (`{ f(), k = g() }`), `return f()` inside a loop, `if a or f() then`, early returns inside nested `if`s, `break` inside nested `repeat` loops, and closures that use a local changed later.
- Fixed: some `for` loops in scripts with debug info came back as a placeholder that threw an error the original never threw.
- Fixed: in some `-O2` scripts with debug info, a variable used in a `repeat ... until` condition was read as a global instead.
- Fixed: some `-O2` scripts with debug info failed to decompile at all.
- More `for` loops in scripts with debug info now print as loops instead of placeholders.
- `function f() end`, where `f` is already a local, no longer creates a second local named `f`.
- Lines match the original more often in scripts with debug info.
- Tables of tables now print one row per line.
- Agents get new tools: `search_source` to search the output, `compare` to diff two results function by function, and `decompile_many` for up to 16 scripts at once. `get_function` finds a function by name and shows what it calls and what calls it. Small scripts (120 lines or fewer) come back with their source right away.
- MCP clients now ask before running `decompile`, since each call uses one of your free decompiles.
- `/v1/decompile` now also tells you where each function starts and ends, which functions call which, and which function each note belongs to.

## 2026.10.27-0719266 · place files without false time-outs

- A script that only waited in line behind others is no longer refused as having run past its time limit: it is answered "busy" and the site tries it again. `maxSeconds` now counts from when the decompiler starts on a job.
- Model names no longer hold a decompile open while the names are chosen, so large `.rbxm` and `.rbxl` files run with fewer refusals.
- Fewer "overloaded" errors when many jobs run at once: a retry now waits a moment before it goes again.
- Free decompiles no longer run out early in the day.
- Members may send files up to 8 MiB, and get 4 hours of decompile time a day, which now includes the naming model's time. Big place files start faster for members.
- Fixed at `-O0` and `-O1`: a local whose only later write was never read printed inline, which could let a weak table drop its key.
- Fixed at `-O2` with full debug info: a function whose calls were inlined could come back with more locals than the compiler accepts.
- With full debug info, output keeps more of the original: `t[8]` stays `t[8]` when a constant local happens to hold 8, an inlined call's parameters print as the caller's locals (`local a = t[1]`), a numeric `for` keeps its index name when its start is an upvalue, and a one-line loop keeps its `end` on that line.
- Older bytecode (version 2): a generic `for` over an iterator the decompiler can't prove now decompiles instead of standing in as a stub.
- At `-O0`, a `for` that returns from inside a `while` under `repeat ... until true` prints as a plain loop instead of a marked one.
- At `-O2`, a chain of more than five inlined functions decompiles to the same program.

## 2026.10.26-554835d · 2048 decompiles at once

- Members can now really run up to 2048 decompiles at once. A big run, such as a whole place file, is spread over several connections, so it finishes in well under a minute instead of stalling.
- The site and the [client for executors](https://unreadable.gg/docs/stream#client) open more connections as a run grows, and close them again once it's done.
- A decompile no longer fails because a heavy one was running next to it. When that happens, it's tried again before you see an error.
- Model names no longer hold up other decompiles: names are added after the decompile itself has finished.
- The place file panel no longer shows a "waiting" countdown or a "starting the naming model" message.

## 2026.10.26-537cd9b · accounts, place files and the stream

- Accounts: make one on the new [account page](https://unreadable.gg/account), with no email or password. The account's number is how you sign in, so keep it somewhere safe. On the same page you make and revoke keys for your programs, which send them as `Authorization: Bearer ugk_…`. See [keys and accounts](https://unreadable.gg/docs/api#accounts).
- A member's decompiles don't count against the free tier. Up to 2048 run at once, with no limit a minute, and each account gets an hour of decompile time a day. Every response says when the membership ends, in `Member-Until`. To get a membership for now, mail [support@unreadable.gg](mailto:support@unreadable.gg).
- Place and model files: drop a `.rbxl` or `.rbxm` on the site. It opens in your browser, only the scripts' bytecode is sent, and you get the file back with every script's source filled in. Sent to the API, a place file gets `422 container`.
- Many decompiles over one connection: the [stream](https://unreadable.gg/docs/stream) is a WebSocket you send jobs on, with a [client for executors](https://unreadable.gg/docs/stream#client).
- Older files: the decompiler reads bytecode versions 1 and 2, early pre-release files and RSB1-wrapped files. These can't recompile to the same bytes, so the output aims for the same program.
- Free tier: 16 decompiles at once (was 2), 120 a minute (was 60) and 5 minutes of decompile time a day. The new `maxSeconds` option (1 to 30) makes a decompile give up sooner. A decompile that times out now uses a free decompile. See [limits](https://unreadable.gg/docs/api#limits).
- The API reads no query strings at all: options and fields go in a JSON body, and any query string gets `400 unknown-field`.
- Closing a request while it decompiles now stops the decompile.
- The viewer: the bytecode pane is gone, so the source takes the full width. The presets sit on the bar, and the switches open in a drawer beside the code. On the home page, "options" sets them before you drop a file, and when you're signed in your choice is kept for next time.
- The docs are now five short pages: an [overview](https://unreadable.gg/docs), the [API](https://unreadable.gg/docs/api), [readability](https://unreadable.gg/docs/readability), the [stream](https://unreadable.gg/docs/stream) and [agents](https://unreadable.gg/docs/agents). Old links still land in the right place.
- Model names arrive sooner, with no wait for the model to start. Output over 200,000 characters keeps its default names. A function stored on a table that uses the table as `self` prints as `function T:m()`.
- Fixed a number of cases where the decompiled code didn't behave like the original, mostly in optimized files: a loop could read a value after it had already changed, an argument could come back as `nil`, functions meant to be made fresh each time came out shared, and some calls ran in a different order.
- Fewer functions come out as `error(…)` stubs, especially in optimized files, loops that skip ahead with `continue`, long `and`/`or` conditions and very large modules.
- `-g2` keeps more of your names: loop variables, declaration lists, swaps and the names around inlined calls. `for _, b` no longer prints as `for _1, b`. `-g1` keeps each statement on its recorded line, and `-t1` brings back the types of loop variables and upvalues.
- `@native` is printed on each function that had it, and a local named `goto` keeps its name.
- Fixed hangs: a module that never finished at `-O2`, deeply nested `if` expressions, and a small place file that could use gigabytes of memory.
- Large files decompile faster and use less memory.

## 2026.10.16-536f5dc · readability presets and model names

- Choose how the output reads: `faithful` (the default, unchanged), `balanced` or `readable`, or turn single switches on and off, in the viewer or through the API. A switch changes only how the code is written, never what it does. See [readability](https://unreadable.gg/docs#readability).
- The switches: roll an unrolled loop back into `for i = 1, 3 do ... end`, return early with `if not c then return end` instead of nesting the rest of a function in one `if`, write `n *= 3` for `n = n * 3`, drop unused values and branches, write `16` for `2 * 8` or `math.pi / 2` for `1.5707963267948966`, write a small helper's call as its body, add blank lines between unrelated steps, add the types inference proves, and note on each function's first line which outer variables it captures, like `-- upvalues: speed (copy), pos (ref)`.
- Model names: with `balanced`, `readable` or the `modelNames` switch, a naming model names the values your file left unnamed, so `local v3 = arg0.Health` can read `local health = player.Health`. It never picks a name that could change the program, and if the model is busy or down you get the usual names and a note saying so.
- Cleaner default output, with the same meaning: methods print as `function Spring:getTarget()` using `self`, functions as `function f()` rather than `f = function()`, services and modules take their names, as in `local Players = game:GetService("Players")`, non-empty tables print as `{ 1, 2 }`, guard clauses get a line each, and `t.a.b -= e` no longer goes through a temporary.
- At `-g2`, a function that captures outer variables keeps its own name, where before it printed as `v1`.
- Fixed two cases where the output ran differently from the input: a captured local first set by an `if a then b else c` expression, and an unused function inside a `do` block at `-O0`.
- The first line now says only how long the decompile took, plus the naming time when names came from the model: `-- unreadable.gg ~ 140ms (+3.2s naming)`. The setting used comes back as `setting` in the result.
- Agents can use the decompiler through its [MCP server](https://unreadable.gg/docs#mcp), and send a file straight from disk with `POST /v1/handles` rather than passing it through their context.
- The API can stream progress: send `Accept: application/x-ndjson` and get one line per stage (decompiling, renaming, done).
- The `unverifiedWarning` option is gone. A file whose opcodes are numbered differently from stock Luau gets an `unverifiable` [note](https://unreadable.gg/docs#notes), never a comment at the top of the source.
- Large files decompile faster and use less memory.

## 2026.10.12-3553b00 · 100 free decompiles a day during the beta

- Beta only: while unreadable.gg is in beta, you get 100 free decompiles a day instead of 5, on the site and through the API together. The limit will change when the beta ends.
- Cleaner `-O2` output: functions the compiler inlined, even deeply nested ones, print as calls like `f(1) + f(3)` again, not as long runs of copied code.
- Large files decompile up to 4× faster while using half the memory.
- Hand-edited or malformed files: where the output can't match exactly, it now carries a [note](https://unreadable.gg/docs#notes) instead of looking clean. For example, a function that uses more locals than the compiler allows, or that reads `...` without declaring it.

## 2026.10.11-7046806 · the site and the api are live

- Decompile a file in your browser: drop it on [unreadable.gg](https://unreadable.gg). The source shows beside your file's bytes, with its verdict, ready to copy or download.
- Scripts and agents can send the same file to `POST https://api.unreadable.gg/v1/decompile` and get back the source, a [verdict](https://unreadable.gg/docs#verdicts), [notes](https://unreadable.gg/docs#notes) and the version that wrote it. See the [API docs](https://unreadable.gg/docs).
- Every connection gets 5 free decompiles a day, on the site and through the API together, with no key or account. Errors and refused decompiles don't count.
- Your files are never stored or logged: see [privacy](https://unreadable.gg/privacy).

## 2026.10.11-4227ea3 · bytecode version 14

- Reads bytecode version 14 and the calls current Luau compilers write by default. Modules from them used to be refused.
- Notes and errors carry fixed codes a program can act on, such as `function-refused` and `not-luau-bytecode`: see [notes](https://unreadable.gg/docs#notes) and [errors](https://unreadable.gg/docs#errors).
- A function the compiler kept from code it removed, such as the body of `if false then … end`, is printed back inside `if false then … end` instead of being left out. Where it has to guess what the function captured, the guess is marked.
- Fixed a case where the first statements of a loop could be left out of the output.
- The parameters of a function that also takes `...` keep their names, and so do locals that are copied and read later.
- `local a, b = -0.5, #x` prints as one list, as it was written.
- Very large modules decompile much faster at `-O0` and `-O1`.
- Three more shapes are refused rather than guessed: a branch whose inner arm computes a value, a table constructor whose parts don't agree, and a function that needs too many passes to settle.

## 2026.10.03-4be51f9 · `continue` in nested loops

- A `continue` inside nested loops comes back as `continue`.
- An assignment to several places at once, such as `a.x, b.y = 1, 2`, comes back as one statement.

## 2026.09.27-1dc49cb · fewer wrong programs

- Fixed several cases where the output could have run differently from the input: assignments to undeclared and captured variables, and generic `for` loops left by a `return`. Where one can't be printed faithfully yet, it's refused instead.
- A module that had started taking seconds to decompile is back to a few hundred milliseconds.
- The decompiler builds for WebAssembly, which is how the site and the API run it.

## 2026.09.20-55cbf5f · conditions as one expression

- Conditions with three or more tests print as one `and`/`or` expression.
- A variable a loop carries from one pass to the next is no longer declared again on every pass.

## 2026.09.02-39ae4d2 · `and`/`or` as values

- `and`/`or` chains used as values fold back into one expression, as in `local x = a and b or c`.
- A damaged or crafted file can no longer make the decompiler reserve memory for more than the file holds: every count it reads is checked first.

## 2026.08.30-f0c3823 · table constructors and recorded types

- Table constructors come back as `{ … }` instead of a table filled in one assignment at a time.
- A type the bytecode records for a local is printed with it, as in `local w: number`.
- `if not` keeps its two arms in the order they were written, and a `do … end` block keeps its locals inside it.
- A statement that starts with `(` gets a `;` before it, so it isn't read as a call on the line above.
- A file from another Lua version is refused by name instead of read as Luau. See [Lua versions](https://unreadable.gg/versions).

## 2026.08.23-38b2adc · first version

- Decompiles Luau bytecode back into source, and marks or refuses what it can't reproduce instead of guessing.
- Reading a file can't crash the decompiler: a crafted file that would overflow its stack is turned away, and nothing on the way in may abort.
