Skip to content

Troubleshooting

This page is a directory of what Twill reports. Each message it writes is quoted below exactly as it appears in the Output, so pasting the line you are looking at into search lands you on its cause and its fix.

A bold entry is either a message quoted word for word or a symptom described plainly, and the paragraph under each one is the reason and what to do about it.

  • Have a message? Search this page for its text. The quote is verbatim, so a partial phrase is enough to find it.
  • Have a symptom but no message? Find the section for the area you are in from the list below, then read down it; the symptom entries sit alongside the messages.
  • Have neither? Start at When nothing here matches.
AreaCovers
Installation and requiresMissing folders, server-only modules, silent no-ops
Boot and LifecycleService discovery, Init and Start, critical failures
Player dataSessions, saving, offline edits, migrations
StorageWhat Store reports about keys and the DataStore itself
NetworkingDeclarations, handlers, rates, ranks, decoding
ReplicationSubscriptions, patches, paths, published state
PermissionsRanks, resolvers, group lookups
Admin consoleCmdr, the command gate, built-in commands
MonetizationProducts, passes, receipts
LeaderstatsBindings, value objects, sorting
FilteringText filtering and its failure mode
Studio and toolingThe Edit VM, API services, testing purchases

Every line carries the scope that wrote it, and anything at warning level or above also names the nearest line outside Twill.

[Twill.Data] (from MyGame.Services.Shop:42) main.Home for 123 holds Vector3; run it through Twill.Serialize.Encode first
^ who wrote it ^ who caused it

That second part is usually enough to skip opening a stack trace at all. When it is absent, the call came from inside the framework or through too many layers of native code to attribute honestly.

The default level is Info, so Debug lines are hidden. Raise it and the framework says more, including full error traces from Error.Install.

Twill.Log.SetLevel("Debug")

[Twill] ServerScriptService.Twill.Net is missing; the server half is not installed

ServerScriptService.Twill is absent or renamed. Both modules are required, and the names matter because Twill finds its own server half by name. The module named in the message is whichever one was needed first. See Installing Twill.

[Twill] ServerScriptService.Twill.X is missing; ServerScriptService.TwillServer is from Twill 1, so replace it with ServerScriptService.Twill

A Twill 1 server folder is installed and the Twill 2 server module is not. Install both modules from the release and delete TwillServer. See Before upgrading.

[Twill] ReplicatedStorage.Twill is missing; install both halves from the same release

ServerScriptService.Twill was required, but the shared module beside it is absent or renamed.

[Twill] ReplicatedStorage.Twill is X but ServerScriptService.Twill is Y; install both halves from the same release

The two modules come from different releases, usually after updating only one. Replace both from the same .rbxm.

ServerScriptService.TwillServer is left from Twill 1 and is no longer read; delete it

Twill 2 is installed and working, and the old server folder is still there. Nothing reads it; delete it.

[Twill] 'X' is not an installed kit, or ..., or has no client half on a client

No kit under Kits answers to that name on this side. Check the spelling, and that the kit’s halves sit in Twill.Kits in ReplicatedStorage and, for a kit with server logic, in ServerScriptService.

[Twill] 'X' is not a Twill module, or is server only on a client, or [Twill] 'X' is not a Twill module on the server

Either the name is misspelled, or you reached a server-only module from the client. Anything holding saved data, purchases, filtering, unpredictable draws, or signed secrets never exists on a client. The module reference marks which is which.

[Twill] Net.Handle is server only and the same for Authorization.Configure, Authorization.SetRank, GetGroupStanding, InGroup, Admin.Configure, Admin.Register, Admin.RegisterTypes, Admin.Run

The module itself exists on the client, but this particular member does not. Reading a rank on a client is fine; deciding one is not.

Lifecycle.SetPlayerGate is server only

The client has no player pipeline, because there PlayerAdded means somebody else joined.

Nothing happens at all, and there is no message

Nothing required Twill. A module that is never required never runs, which most often bites on the client: see the replication and admin console entries below.

A service never boots, and nothing is reported

It must be a direct child of a folder passed to Start, must be a ModuleScript, and must return a table. A module returning anything else is skipped silently, which is what keeps a helper module sitting beside a service from booting as one.

Check Lifecycle.GetBootOrder() for what was actually discovered.

'X' at Y is a folder; pass it to Start to boot what is inside it

Nesting a folder inside a service folder does not extend the search. Pass the inner folder to Start as well:

Twill.Lifecycle.Start({
ServerScriptService.Services,
ServerScriptService.Services.Combat,
})

duplicate service name 'X' at Y; skipped

Services are looked up by name, so names must be unique across every folder passed to Start. The second one found is ignored.

failed to load X: ...

The module threw while being required, before any hook ran. This is ordinary Luau error: read the message after the colon.

'X' errored in Init: ... or 'X' errored in Start: ...

The hook threw. The rest of the boot carries on unless the service is marked Critical.

SetPlayerGate called after Start; players already joined were not held

The gate was installed too late to hold the players already on the server. Set it during Init.

Start called more than once; ignoring

Lifecycle.Start runs once per side. If you need more folders, pass them all in one call as a list.

A service sees another service that is not ready

You called it from Init. Move the call to Start, which runs only after every Init has finished.

A Start hook depends on another Start having finished, and sometimes fails

Each Start runs on its own thread, so boot order decides when one begins, never the order in which they finish. Have the later one ask for what it needs rather than assume it exists.

Lifecycle.Get has no service named 'X'

Lifecycle.Get was given a name that was never discovered. Usually a typo, and occasionally a service that failed to load earlier in the same run.

Everyone is kicked with This server failed to start.

A service marked Critical threw in Init or Start. The reason is in the kick message and in Lifecycle.GetFailure(). Players arriving afterwards are kicked on sight until the server is replaced.

boot failed: ... on the client, and nothing else runs

The same failure on the client, where there is nobody to kick. The client boot stops there.

Players are kicked with Your saved data could not be loaded.

The session would not open. Usually the DataStore API is having trouble, or a migration step threw. Look for the [Twill.Data] lines above it.

Players are kicked with Your data session was claimed by another server.

Normal when someone changes servers faster than the old session is released. Their branches are freed at the same moment.

Players are kicked with This server failed to load your session.

The player gate itself threw, rather than the session failing to open. If the gate is Data.Gate, the cause is above it in the log; if it is your own gate, the error is in it.

Data.Gate used before Configure

Lifecycle.SetPlayerGate(Data.Gate) ran before Data.Configure. The same message exists for GetOffline, Edit, and Reset.

Data is already configured

Configure runs once. Configuring twice would change the shape of stored data underneath a running server.

a branch cannot be named 'main'

main is the scope name for the primary profile, so a branch cannot take it.

Data has no branch named 'X'

The name does not match anything in Branches. Data.ListBranches() reports what exists.

Data.Edit answers "unsupported", with X.Y for 123 holds Z; run it through Twill.Serialize.Encode first

The value holds something a DataStore cannot store. Encode it, or fix the shape of its keys. See Store Roblox values safely.

Data.Edit answers "unknown"

No scope answers to that name. The primary profile is "main", not "Data"; Data is the replication key, which is a different thing.

Data.Edit answers "blocked"

Something along the path is held by a value that is not a table, so the write would have to overwrite it. Nothing was written.

Data.Edit answers "queued" and the change never lands

That is the truth rather than a fault: the user is not on this server, so the edit waits for a server that holds them. For a user who never returns, it never lands.

applied a queued edit to 'X'

Informational. An edit aimed at this player from elsewhere has arrived.

dropped a queued edit to 'X': refused

An edit sent from another server arrived, but the scope’s Validate check refused the result, so nothing changed. blocked in the same place means a step along the path is not a table.

an edit to 'X' in 'Y' was refused, so nothing changed: ...

Data.Edit or Data.Reset tried the change on a copy, and Validate refused it. The call returned "refused", and the live data is untouched.

could not read 123: ... or stored data for 123 could not be upgraded: ...

GetOffline failed to read, or the stored data failed a migration. It answers nil rather than a partial profile.

could not open branch 'X' for 'Y'

The branch session would not open. Every caller waiting on that load is answered with nil rather than left waiting.

stored data is at version N, past the M this server upgrades to; its version is left alone

The profile was written by a newer server than this one. It is read as it stands and its version is not moved backwards. Roll the place forward rather than migrating down.

Data changes are not saving

Check three things in order: that you mutated the table Data.Get returned rather than a copy of it, that Data.IsReady(player) is true, and that Studio Access to API Services is enabled.

'X' migration to version N failed: ...; their stored data was left exactly as it was

A migration step raised. The player was kicked, and the steps ran on a copy, so nothing they had is lost or half converted. Fix the step, and the next load runs it again from the version the data carries.

storage cannot be reached from this Studio session (...); player data lives in memory ...

Studio Access to API Services is off, or Studio is offline. Players load from the template, and nothing is saved when the session stops.

These come from Store, underneath Data. Most of them name a key rather than a player.

'X' was taken again here, so what was held before it can no longer be written

The same key was opened twice on this server, and the earlier handle is now inert. Whatever held it should let it go; the newer session owns the key.

'X' in 'Y' was not written, and keeps what it held before: Validate refused it: ...

The store’s Validate check refused what the key holds, so the save was skipped and the last good data stays stored. A check that raised reads Validate raised an error: .... Until the data passes again, no later save lands either, so find what put it in that state.

'X' in 'Y' was not written, and keeps what it held before: 'F' holds ..., which storage cannot keep

The data holds a value a DataStore cannot store, at the field named. The save was skipped before storage was asked, and the last good data stays stored. No later save lands until the value is gone. Encode it with Serialize or remove it.

'X' in 'Y' holds N of the 4194304 characters a key may hold; storage refuses a write past that

The key is past 3,000,000 characters. Saves still land, but one past the limit fails, and every save after it. Trim what the key holds, move part of it to a branch, or turn Compress on for the store.

'X' in 'Y' could not be written: ...

A save was refused, and the reason says which of the two applies: another server holds the key now, or the key no longer holds anything this store wrote. Neither is retried, because retrying would write over whoever holds it.

'X' could not be taken within N seconds

The session did not open inside StartTimeout. The usual cause is a server that has not yet released the key, and the usual outcome for a player is the load failing rather than waiting longer.

'X' holds something this store cannot read, and was left alone

The key holds a value that is neither a Twill envelope nor a ProfileStore one. Nothing is overwritten, because overwriting is how data is lost. Read it with playerdata versions before deciding what it was.

'X' could not be packed to be written, so it was not taken

Compress is on for the store and the data could not be packed. The key is left as it was. Look for a value Compress refuses, such as a cyclic table.

storage is failing repeatedly; the last of it was '...'

The DataStore has refused several requests in a row. Twill backs off and keeps trying. Check the Roblox status page before changing anything.

storage cannot be written from here (...); nothing will be saved

The reach probe answered that this place cannot write. In Studio this is Studio Access to API Services being off.

storage never said whether it could be reached; carrying on as though it can

The probe itself did not answer in time. Saving is attempted anyway, because refusing to try is worse than trying and failing.

N key(s) were still being written when this server closed

Shutdown ran out of patience with writes still in flight. Those writes may or may not have landed. It points at a save that takes too long, usually a profile that has grown.

'X' was already declared as (...) and cannot be redeclared as (...)

Two places declared the same remote with different types. Make them agree. This refusal is what stops one caller from serialising through another’s types.

Net.Get has no remote named 'X' yet

Net.Get ran before whatever declares that name. Declaring is idempotent, so the usual fix is to declare it in a shared module both sides require.

'X' already has a handler

One handler per remote. Net.IsHandled lets a module check rather than claim and be refused.

'X' replies to the caller, so it needs a Reject option

A remote that replies must say what a refused caller is told, otherwise a refusal leaves them waiting forever.

Rate for 'X' must be above zero

Metering always applies. A rate of zero would refuse everything, so it is rejected as a mistake.

Calls are silently dropped, and the log says 'P' is over the rate for 'X' (N call(s) refused)

The caller exceeded the remote’s rate. Refusals are reported at most once every few seconds per player and remote, with a count, so the log cannot become an amplifier for the flood it is refusing.

'P' lacks the rank for 'X' (N call(s) refused)

MinimumRank refused them, before any of their allowance was spent.

A refusal naming a field, such as 'P' Stats.Coins should be a whole number for 'X'

The Schema rule for that argument did not match. The reason names the position that failed.

validator for 'X' errored: ...

Your Validate threw. A test that cannot decide is read as a refusal, never as consent.

handler for 'X' errored: ...

Your handler threw. A remote that replies answers with Reject; one that does not drops the call. Either way the failure never reaches the calls behind it in the same message.

'X' was declared here but the server never declared it; calls on it go nowhere

A name declared on this client that no server module declares. Calls on it are dropped locally rather than reaching the wire. Usually a misspelling, or a shared remotes module the server never requires.

'X' was called before the network was up more often than it holds

Calls made before the wire ids arrive are held and sent once they do, and that hold has a ceiling. Reaching it means a client is calling a remote far ahead of the network being ready.

an unreliable call was larger than one unreliable message carries, and was dropped

An unreliable remote was given more than fits in a single unreliable message. There is no second chance for one of those, by definition. Send less, or declare the remote reliable.

a call this side could not read

A call arrived that the declared types could not decode. The call is dropped and the ones behind it in the same message still arrive. From a client this is ordinarily somebody probing the remote; between your own two sides it means the declarations have drifted apart.

a remote is listened to on a client; the server serves one with Net.Handle ...

Remote:Connect, Once, or Wait was called on the server. A server listener would receive calls with no metering. Serve the remote with Net.Handle.

a call could not be written (N time(s)): ...

A value did not fit its declared type, and the call was dropped at the sender. The rest of the message is unaffected. The reason names the field.

The client never receives anything

The client announces itself the first time its half of the module is required. If no client code ever requires Twill.Replication, the server has nothing to send to. Requiring it once anywhere in your client boot is enough.

the server never registered replication; nothing will arrive

The client waited for the server to declare the replication remotes and gave up. The server half never loaded, which usually means the server never required Twill.Replication either.

patch arrived for 'X' before its value did; ignored

A change arrived for a key this client has never held. Applying it would build a value the server never published. In practice it means the readiness signal was missed rather than a message being lost.

'X' could not be sent to Y (N time(s)); it goes again on its next write

The key’s value could not be written to the wire: it holds something Types.Any cannot carry, such as a function, or it is past the server to client bounds. The player keeps what they last received. Every other key still arrives.

A subscription never fires

Check whether the key name contains a dot. The first dot separates the key from the path, so a key called "Player.Data" is read as key Player, path Data, and can never match.

path 'X' inside 'Y' is blocked by a non-table

A step along the path is held by something that cannot be descended into. Nothing was written, and SetPath answered false.

'X.Y' is not a number

Increment found something other than a number at the path. A missing value is fine and counts from zero; a string is not.

validator for 'X' refused a write or validator for 'X' errored: ...

The key’s guard rejected the value, or threw and was read as a rejection. Clearing a key is always allowed regardless.

OnChanged("Data") fires constantly

That is the behaviour when Replicate is configured: the view is rebuilt on an interval. Network traffic is unaffected, because a message is only sent when something moved. To hear real changes, subscribe from the client.

A value read on the server does not match what the client has

Something wrote into the value returned by Get or GetFor. Those hand back the published value itself, not a copy. Publish through Set, SetPath, or Mutate instead.

Everyone reads as rank zero

Authorization.Configure has not run, or ran after the check. A player whose rank has not been decided reads as the lowest, so a check made too early refuses rather than admits.

Authorization is already configured

Configure runs once, so that who is privileged cannot change while the server is running.

Resolve errored for 'X': ...

Your resolver threw. The player keeps the rank Users or Default already gave them.

could not read group 123 for 'X': ...

The group lookup failed. It is not remembered, so it is retried rather than settled wrongly. Treat nil from GetGroupStanding as unknown, never as “not a member”.

A promotion does not survive a rejoin

SetRank lasts for the session only. Write it to Data and read it back in Resolve to make it permanent.

F2 does nothing

Nothing loaded Cmdr. It takes two requires: a server one that calls Twill.Admin.Configure, and a client one that requires @game/ReplicatedStorage/Twill/Admin by path.

attempt to yield across metamethod/C-call boundary

You reached Twill.Admin through the root table on a client. The module waits for Cmdr’s client half to replicate, and the lazy accessor on the root table is not allowed to wait. Require the path directly. Once loaded, Twill.Admin works normally.

[Twill] CmdrClient never appeared; the server must require Twill.Admin first

The client waited for Cmdr’s client half and it never arrived, because the server never required Twill.Admin. The server require is what moves it into ReplicatedStorage.

Every command answers Admin commands are closed

Twill.Admin.Configure has not been called. The gate refuses everything until it is told who may do what, which is the only safe direction for that mistake.

Admin commands are not ready yet

The client has not yet received the rank settings through replication. It clears on its own.

You are not allowed to use the console or You are not allowed to run that

The first is the console floor, the second is that command’s own required rank. Both are checked again on the server whatever the client decided.

Too many commands at once. Wait a moment.

Submissions are metered per player. The log records who was turned away and how many attempts went unreported.

Admin is already configured

Configure runs once, for the same reason as the other gates.

A ban of 7d lasts seven seconds

Not from Twill’s own moderation command, which reads durations exactly. This is Cmdr’s built-in duration type, which resolves units by fuzzy match. Check which type your own command declares.

A big: value is whole digits, such as big:1500.

From playerdata set. The big: mark writes a big number, so what follows it has to be digits, with an optional leading minus. Anything else is refused rather than stored as the text big:whatever.

You cannot grant rank N; your own is M

From rank set. The console decides who may run what by rank, so granting one at or above your own would hand out your own authority. The most anyone can create is somebody strictly below themselves. rank also refuses to change your own rank and to touch anybody already at your rank or above.

No store is configured, so there is nothing to save

From saveall, in a game that never calls Data.Configure. Not a fault; a game without saved data has nothing for this command to do.

That is not a seed a round could have revealed

From verifyroll. The seed is not thirty-two bytes of hexadecimal, so it cannot be one Random.Commit produced. Check it was pasted whole. A seed that is well formed but wrong reports a mismatch instead, which is a different answer.

'X.Y' names a path. This action takes a whole key

From repl freeze, unfreeze, or throttle. Those name a whole replicated key, and a dotted path is refused rather than acted on as if it were one. repl get reads inside a key, and takes the path.

Nothing is held at 'X' for everybody

From repl get. Either nothing has written that key yet, or it was written with Replication.SetFor, which gives each player their own copy. Read those with repl getfor <user> <key>.

twill net marks a remote UNSERVED

It was declared and never handed to Net.Handle. A client firing it reaches nothing, silently. This is the diagnosis, not a fault in the console.

A moderation command refuses with X ranks as high as you do

Deliberate. Nobody may act on themselves or on anyone standing as high as they do. The check covers targets present on this server, since somebody elsewhere has no rank to compare.

no handler for product 123; leaving it unprocessed

No HandleProduct was registered for that id, so the receipt is left for Roblox to redeliver. Register the handler and the pending purchase lands on its own.

product 123 errored for 'X': ...

Your grant handler threw. The receipt is answered as not processed, so it is retried rather than lost.

purchase 123 was not confirmed saved; Roblox will retry it

The reward was written but the save was not confirmed in time. The record of what was granted is what makes that retry safe.

product 123 already has a handler

One handler per product. A second is refused rather than replacing the first, since a silently replaced handler stops paying without saying so.

A pass bought during play does not take effect

It should, without any code from you. If it does not, the cache can be cleared with ForgetPasses, but check first that the purchase actually completed.

could not check pass 123 for 'X': ...

The ownership check failed. It reads as not owned and is not remembered, so it is asked again rather than settled against the player.

A stat never appears

The key it watches is not replicated, or the value is one no value object can hold. A value that cannot be shown leaves the stat as it was rather than clearing it.

leaderstat 'X' is already bound

Two systems bound a stat under the same name. Binding adds to the list rather than replacing it, so names have to be unique across every caller.

Leaderstats.Bind expects a list of entries: ...

The list is malformed. The reason names the field that failed.

A big number shows but never sorts

The player list sorts IntValue and NumberValue only. See Count past the number limit for the way around it.

Filter returns nil

The filter could not be reached. That is the designed behaviour: it never returns the text that went in. Decide what to show instead, and never write Filter.ForBroadcast(text, id) or text.

could not check text from 123: ... or could not read filtered text from 123: ...

The platform call failed, or reading the result failed after the check succeeded. Both answer nil.

Filtering is being rate limited

Filter once when text is submitted, store the result, and show the stored result. Filtering on every draw exhausts the per-user limit and achieves nothing.

These come from Shared, Teleport, Board, and Config.

a message on 'X' was not published: ...

Shared.Publish ran out of tries, or this server’s publishing allowance stayed full for the whole wait. Messages are best effort, so nothing that matters may depend on one arriving.

not listening to 'X': this server is at its subscription allowance

The server already holds as many subscriptions as its player count allows. Close subscriptions you no longer need; each one is released when its owner bag closes.

'X' in 'Y' was not written: ... from Twill.Shared

A memory store write failed after its retries. A reason naming DataStructureMemoryOverLimit means the experience’s memory quota is full: shorten lifetimes or remove entries you no longer need.

N player(s) could not be sent to X: ... or 'P' could not be sent to X (Flooded): ...

A teleport failed after its retries, or the platform turned a player down for a reason waiting cannot fix. Teleport.OnFailed fires for each player, with the Enum.TeleportResult.

'P' arrived carrying data, but Token is not configured to read it

The destination place never called Token.Configure. Every place in the experience that receives data needs the same secret.

'X' on 'Y' was not written and waits for the next try: ...

A leaderboard write failed. The change stays queued, merged with anything asked for since, and goes again on the next flush.

configs could not be loaded (...); defaults stand in while this keeps trying

Experience configs are unreachable, usually because the place is not published or configs have never been created. Every key reads its default until they arrive.

'X' was delivered as a string but its default is a number; the default is used

A published config value has a different type from the default declared for it. Fix the value in the Creator Hub, or the default in Config.Configure.

ConfigService: Config value not found for key "X"

Written by the engine, not by Twill, when a declared key has no published value. The default is used. Create the key in the Creator Hub to silence it.

These come from the Trade kit.

Trade.Configure needs Data configured first

Trade.Configure ran before Data.Configure. Configure Data first, in the same Init.

kind 'X' needs Has, Take and Give

A kind of your own is missing one of its three functions. Use a built-in kind, or supply all three.

kind 'X' has a Describe that is not a function

A kind’s Describe must be a function taking the data and the id, or be left out.

CanTrade errored: ...

Your CanTrade rule raised. The ask is refused, and the player is told trading is not possible right now. Fix the rule; it should return false and a reason instead of raising.

Notice: the offer changed; look at it again

A Ready or Confirm reached the server after one of the offers changed. The player’s view holds the changed offers, so they can ask again.

Notice: wait a moment before trading again

The player completed a trade less than Cooldown seconds ago.

trade X was made, but a save has not landed yet; it goes with the next one

The swap was made and both players’ data changed, but a save did not confirm within 20 seconds. The data is still held and saves with the next write. Nothing needs undoing.

trade X failed: ...

Something raised while a trade was being completed, outside the swap itself. When the swap had not run yet, nothing moved, and both players are told the trade failed. When it had, the trade is shown as completed with Saved set to false, and the data saves with the next write. The error names what raised.

Trade.X is client only; its server half decides trades

A client call such as Trade.Request ran on the server, from a require of the client half by path. On the server, use Twill.Kits.Trade, which is the server half there.

These come from the Inventory kit and the Wallet kit. A change either kit turns down is not logged: the call returns false and the reason, so print the second return value.

Inventory is used before Inventory.Configure

A call reached the kit before its setup ran. Call Inventory.Configure in an Init, after Data.Configure. Wallet is used before Wallet.Configure means the same for Wallet.

Default must name one of the containers

Containers lists more than one container and Default is missing, or names none of them. Set Default to the container a call means when it names none.

an item needs a name of 1 to 64 characters with no dot

An item, container, or currency name is empty, too long, or holds a dot. A replicated path is separated by dots, so a name cannot hold one.

at most 5 currencies can be tracked

More than 5 currencies set Track = true. The economy dashboard takes 5.

'Bag' is full

The change would take the container past its Capacity. Capacity counts entries: one for each counted item, whatever its count, and one for each one-of-a-kind item.

'Sword' is removed by the id of the one to remove

Remove or Move was given the name of a one-of-a-kind item. Pass the id that Inventory.Add returned, or one read from the container’s Items.

'Bag' is damaged

The saved field holds something other than the kit’s own shape, from a write made outside the kit. The kit refuses to change it. Repair the field by hand, and pass Inventory.Check to Validate so it cannot be saved that way.

a transaction for 'X' yielded, so it was undone

The function given to Data.Transact yielded, so its changes were undone. Do the yielding work before or after the transaction.

The client halves read nothing until Inventory and Wallet are listed under Replicate in Data.Configure.

Edits to a module appear to have no effect

The Edit-mode VM caches modules between executions. After editing a module’s source, verify through a playtest, not through the command bar.

Studio disconnects repeatedly while testing

Test code that connects to a global service and then destroys its own folder leaves the connection alive. :Destroy() on a parent does not disconnect a connection to Players. Close and reopen the place to clear the Edit VM, and put the connection in a Scope bag.

Data does not persist between playtests

Enable Studio Access to API Services under Game Settings, Security. Without it Studio cannot reach a DataStore, so Data runs on stand-in stores and every session starts from the template.

Discord refuses requests from Roblox game servers ...

Error.Install was given a Discord webhook directly. Reports arrive from Studio and not from a live server. Post through a proxy that forwards to Discord.

Purchases cannot be tested

They cannot be simulated in Studio. Publish to a private test place and buy with a real account.

  1. Raise the level with Twill.Log.SetLevel("Debug") and reproduce.
  2. Install Error.Install so failures nobody handled leave a record with their trace.
  3. Read the call-site in the warning. It names the line outside Twill that caused it, which is usually the one to change.