About roblox-studio-workflow
roblox-studio-workflow is a workflow guide for Rojo-based Roblox projects, covering code authoring on disk, synchronization, linting, and testing in Studio. It details common sync and runtime pitfalls, including how to verify changes through the Studio MCP.
Key features
- 01Keep Rojo as code source of truth
- 02Sync changes in Studio edit mode
- 03Lint Luau with selene
- 04Test through remotes and observable effects
- 05Check persistence fields end to end
- 06Troubleshoot sync and runtime pitfalls
Use cases
- 01Applying code changes before Studio tests
- 02Verifying game behavior through remotes
- 03Checking saved player data fields
- 04Diagnosing stale Rojo sync or boot failures
MCP support
The workflow uses Studio MCP to test and inspect a running Roblox game, while reserving MCP for map or instance edits rather than authoring Luau. It refers to the roblox-studio-mcp skill for the available tools and their caveats.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ flatbedj/ roblox-skills --skill roblox-studio-workflowAfter installing, type/roblox-studio-workflowin Claude Code to confirm it loaded.
Details
From the project READMERoblox Studio dev workflow (Rojo + selene + Studio MCP)
The reliable loop for a Rojo-based Roblox project. Author code on disk, sync via Rojo, test via the Studio MCP.
Golden rules
- Author ALL Luau on disk (
src/), never in Studio via MCP. Rojo is the single source of truth. Editing scripts in Studio creates duplicate instances when Rojo re-syncs (two copies of a module; the orphan goes stale). Reserve MCP for map/instance edits + testing/inspection only. --!strict+ TABS for indentation (match the project's house style exactly). When Edit toolold_stringfails on whitespace, the file uses tabs — copy the exact leading tabs.- Lint after every change, keep it clean: the project usually has selene (
selene srcor~/.rokit/bin/selene src). Target 0 errors / 0 warnings / 0 parse errors. selene does NOT catch two big runtime traps — see gotchas below.
The sync cycle (CRITICAL — get this wrong and you test stale code)
Rojo only applies patches while Studio is in EDIT mode, NOT during Play. Edits made while playing queue and land when play stops. So to test disk changes:
start_stop_play→ stop (is_start: false)- Wait ~10–15 s for Rojo to flush the patch (don't skip this — a fast restart runs stale code)
start_stop_play→ start (is_start: true)get_console_output→ confirm a clean boot before testing
If the Rojo plugin's websocket silently drops ([Rojo-Warn] Disconnected in console), NOTHING syncs until the user reconnects the plugin. If duplicate instances appear: Rojo plugin → Disconnect → delete the synced roots (e.g. ReplicatedStorage.Shared, ServerScriptService.Server, StarterPlayerScripts.Client) → Connect once for a clean rebuild.
Testing a running game (via the Studio MCP)
Drive the game through the MCP — see the roblox-studio-mcp skill for the tools + their gotchas (datamodel_type, the separate-VM rule, capture/input quirks, dead-link fixes). The testing STRATEGY:
- Drive via the game's own remotes, not module state.
execute_luauruns in a separate Lua VM, sorequired modules are fresh copies andPlayerData.get(plr)returns nil there. Instead call the game's RemoteFunctions/Events (Net.func("X"):InvokeServer(...), an Admin/debug remote) or read/write Instances directly.FireAllClientsmust run from theServerdatamodel. - Verify via observable side-effects, not a module's internals. Assert on a label's
.Text, an accessory count,ImageLabel.Imagewhen walkingPlayerGui:GetDescendants(). Pure helpers CAN be unit-tested with synthetic inputs in the execute VM. - Pace side-effectful loops to real cooldowns — an action with a server cooldown rejects rapid calls, which reads as failures. Space them out.
- Confirm a clean boot with
get_console_outputbefore trusting a test (catches silent failures like the 200-local overflow or a stuckWaitForChild).
Engine / language gotchas (selene misses these — they bite at RUNTIME)
- 200-local ceiling in a big main chunk (e.g.
init.client): adding a top-levellocalthrows "Out of local registers: exceeded limit 200" at runtime (selene does NOT catch it, boot silently fails). Fixes: bundle related state into ONE table local (local ui = {}thenui.x,ui.y), hang module refs on a table field (ui.mod = require(...)), wrap a group indo … end, or inline single-use locals. Function-body/closure locals do NOT count toward the main chunk. - selene + newer classes:
Instance.new("SomeNewClass")fails selene's stdlib check if the class isn't in its list (e.g. very new Instance types). If the instance is auto-created by its service, justFindFirstChildOfClass/FindFirstChildit and set properties instead ofInstance.new. - UI-specific traps live in the
roblox-ui-hudskill — font glyphs (✕→X, no flag emoji), click-outside-to-close, horizontalUIListLayoutorder, theUDim2.fromScalelint, and the boolean-visibility bug. Load that skill for any UI/HUD work.
PlayerData persistence checklist (a classic source of silent data loss)
For every saved field, confirm ALL of: type annotation · default (init) · serialize · load · reset. A field saved but not loaded silently resets on rejoin, then the next autosave writes the reset value back = permanent loss. When you add a new stat/upgrade key, grep the load function to ensure it restores it (prefer a loop over the saved sub-table so new keys are future-proof).
(Driving Studio itself — running Luau, screenshots, generating assets, uploading images, fixing a dead MCP link — is the roblox-studio-mcp skill.)