Documentation
Cocktail handbook
Written against build 1.4.2. Where an API changed recently, the changelog entry says when.
Install
Cocktail is Linux-only, x86_64. Grab a package from the download page, verify it, and install it. sudo is needed once for the package itself — afterwards the workspace, the engine and every instance run as your own user.
# verify, then install
sha256sum -c cocktail-1.4.2.sha256
gpg --verify cocktail-1.4.2.sig
# Debian / Ubuntu
sudo apt install ./cocktail_1.4.2_amd64.deb
# Fedora
sudo dnf install ./cocktail-1.4.2.x86_64.rpm
# Arch
yay -S cocktail-bin
# or no install at all
chmod +x Cocktail-1.4.2-x86_64.AppImage
./Cocktail-1.4.2-x86_64.AppImage --workspace ~/researchTested against Ubuntu 22.04, Debian 12, Fedora 37 and Arch. Anything with glibc 2.35 and kernel 5.15 or newer should work; the AppImage is the fallback when your distribution is not one of those.
Your first script
Open a workspace folder, create main.luau, and run it with F5. The terminal below the editor belongs to the same session you are editing — what you read there is the run you just triggered, not a copy of it.
local rt = require("cocktail.runtime")
local sess = rt.attach({ instance = "lab-01", sandbox = true })
sess:on("frame", function (f)
if f.index % 60 == 0 then
print("second", f.index // 60, "cost", f.cost_ms)
end
end)
sess:run({ frames = 300, budget_ms = 0.4 })
print("drift", sess:drift())If attach cannot find the instance it returns nil plus a reason string rather than throwing, so a harness can retry without wrapping every call in a pcall.
Workspaces
A workspace is a folder plus a cocktail.toml at its root. It is plain text and meant to be committed.
[workspace]
name = "client-regression"
entry = "main.luau"
ignore = [".git", "out/"]
[sandbox]
fs = ["read:./fixtures"]
net = []
process = []
[run]
budget_ms = 0.4
frames = 300Runtime API
rt.attach(opts) → session | nil, reason
Connects to a running instance. opts.instance is a label from the instance manager. opts.sandbox defaults to true and should stay that way unless a test genuinely needs a capability.
session:step(opts)
Advances exactly one frame under a millisecond budget. Returns a frame record with index, cost_ms, alloc_kb and suspended.
session:run(opts)
Steps opts.frames times, emitting the frame event each time. Equivalent to a loop around step, except the scheduler batches the bookkeeping.
session:drift()
Accumulated difference between requested and actual frame timing, in milliseconds. A healthy run reports 0.00; anything past roughly 2.0 means the budget is too tight for the work in the script.
session:record(path) / rt.replay(path)
Writes a replay file, or plays one back frame-for-frame. Replays are how bug reports should be filed — they reproduce without needing your environment.
Instance API
The grid in the UI is scriptable. Groups are just labels, and an instance can belong to several.
local im = require("cocktail.instances")
im.launch({ label = "fuzz-a", group = "fuzz", reconnect = "always" })
im.launch({ label = "fuzz-b", group = "fuzz", reconnect = "always" })
-- push one script to a whole group, collect results
local results = im.broadcast("fuzz", "harness.luau", { timeout_s = 30 })
for label, r in pairs(results) do
print(label, r.ok and "ok" or r.error, r.elapsed_s)
end
im.stop("fuzz")broadcast returns once every row has reported or timed out. Each result is independent — one failure does not cancel the others.
Decompiler
It reconstructs control flow rather than dumping instructions, and marks anything it inferred with a comment so you can tell recovered code from certain code.
local dec = require("cocktail.decompile")
local src = dec.module(target, {
names = "recover", -- "recover" | "keep" | "index"
inline = false, -- keep helper calls separate
comment = true, -- annotate uncertain reconstructions
})
-- whole tree, in parallel
dec.batch({ out = "out/", jobs = 8 })Recovery rates depend entirely on what debug information survived compilation. Expect near-complete local names when debug data is present, and v1…vN placeholders when it is not.
Sandbox & capabilities
Scripts start with no filesystem, no network and no process access. Capabilities are granted per workspace in cocktail.toml, and every grant that is actually used is written to the session log alongside the line that requested it.
fs—read:andwrite:prefixes, scoped under the workspace unless an absolute path is given.net— an explicit host allowlist. There is no wildcard.process— spawning is off by default and must be granted explicitly per workspace.
Benchmarks
The numbers on the homepage come from a fixed harness so they can be reproduced and argued with.
- Median frame cost —
bench/frame.luau, 600 frames, budget 0.4 ms, on a Ryzen 7 5800X running Ubuntu 24.04 with the client windowed at 1080p under X11. We report the median of 20 runs, not the best one. - Cold attach — process start to first successful
step, machine idle, page cache dropped, defaultptrace_scope=1with the instance launched by Cocktail. - Session success rate — sessions that ran to completion without an engine-side error, across telemetry opted-in users over the trailing 30 days.
- Instances per host — the supported ceiling, measured with 16 GB RAM. It is a support limit, not a hard cap in the code.
The harness ships in the install directory under bench/. If you measure materially different numbers on comparable hardware, open a thread — we would like to know.
Troubleshooting
Attach fails with E_NO_INSTANCE
The label does not match a running row. Check the instance manager, and remember labels are case-sensitive.
Attach fails with E_VERSION_SKEW
The client updated after your build shipped. Check the changelog — a retargeted build usually lands within a few hours.
Frames report suspended = true constantly
The script cannot finish inside its budget. Raise budget_ms, or move the expensive work off the per-frame path.
Attach fails with EPERM
Almost always ptrace_scope. Most distributions ship 1, which allows attaching only to direct children — fine when Cocktail launches the instance itself, not fine when you attach to a client that was already running. Check it with cat /proc/sys/kernel/yama/ptrace_scope and relax it for the session if you need to:
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scopeThat resets on reboot, which is the behaviour you want. Do not make it permanent on a machine you use for anything else.
The workspace will not start on Wayland
Native Wayland landed in 1.3.7. On older builds, or on a compositor that refuses the session, force XWayland with GDK_BACKEND=x11. The headless CLI runner needs no display server at all.
Acceptable use
Cocktail is for software you own or are explicitly authorised to test: your own builds, a client that has engaged you, a bug bounty whose scope covers it, or a platform whose terms permit the testing you are doing.
It is not for interfering with other people’s sessions, gaining an advantage in live multiplayer, or stripping protections from software you do not own. Accounts used that way lose access, and we cooperate with abuse reports from publishers.
If you are unsure whether your work is in scope, ask before you attach.