Fixing a Rejection
Paste the code from your rejection into your browser's find-in-page (Ctrl/Cmd+F) on
this page. Every row links to the full rule on What Gets Rejected
for the exact source and message. None of these fixes involve loosening a rule — every
fix here is something you change about your mod, never about the scanner.
Script-content patterns#
If your code is one of these five, go straight to Script Safety
— it has the exact regex, a verified list of what does and doesn't trip it, and safe
renaming patterns. Quick summary:
| Code |
You changed nothing gameplay-relevant, but you're still rejected because… |
Fix |
dom-access |
your script text contains the standalone word window, document, parent, top, or opener — in code, a comment, or a string |
Rename the identifier to a compound form (parentZone, deckTop) or rephrase the comment/string. See Script Safety § dom-access. |
network-access |
your script text contains fetch, XMLHttpRequest, WebSocket, or EventSource |
If it's real code: remove it — mods cannot open their own network connections; use the api RPC surface instead. If it's a comment/string: rephrase. See Script Safety § network-access. |
storage-access |
your script text contains localStorage, sessionStorage, indexedDB, or cookie |
If it's real code: switch to api.getSavedData / api.setSavedData (capability saved-data). If it's a comment: rephrase. See Script Safety § storage-access. |
dynamic-code |
your script text contains eval, Function, or importScripts |
If it's real code: remove it — there is no supported replacement, dynamic evaluation is not permitted. If it's a JSDoc type ({Function}): lowercase it to {function} or use an arrow-type signature. See Script Safety § dynamic-code. |
timer-loop |
your script text contains setInterval or requestAnimationFrame |
Move the recurring work onto api.on(...) event hooks (capability subscribe-events) instead of a client-side loop. A one-shot setTimeout is fine — it isn't in this pattern. See Script Safety § timer-loop. |
Commit pinning#
| Code |
Symptom |
Cause |
Fix |
unpinned-scan |
Registration succeeds but the mod comes back incompatible, with no other error to explain it. |
The ref could not be resolved to a commit sha, or a supplied commitSha was not confirmed by GitHub as a commit, so the scan could not be pinned — the server would have been reviewing one commit and serving whatever the branch pointed at later. |
Publish through DiceyTable, which supplies the sha it merged. Registering by hand? Pass the full 40-character commitSha of a real commit (an abbreviated sha, or a branch named like a sha, is refused), or omit it to pin whatever the ref resolves to. Self-hosting? Set GITHUB_TOKEN on the server — the lookup is unauthenticated without it and GitHub allows 60 requests per hour for the entire server. |
Reserved ids#
| Code |
Symptom |
Cause |
Fix |
reserved-pack-id |
Registration comes back incompatible before any asset is checked. |
Your manifest id is a pack built into DiceyTable, such as diceytable.dining-table. Clients load that id from the app itself, so your mod could never be the one that loads. |
Change id in diceytable.mod.json to your own id and republish. To use the built-in Dining Table, assign it from your game's Table row instead. |
Path & asset rules#
| Code |
Symptom |
Cause |
Fix |
unsafe-path |
An asset, entry.setup, or entry.script path is rejected. |
The path starts with /, contains .., contains a backslash, or is a full http(s):// URL. |
Use a plain forward-slash path relative to the repo root, e.g. assets/models/board.glb. |
unsupported-asset-type (manifest) |
A manifest.assets[] entry is rejected by extension. |
The file's extension isn't one of the 18 supported extensions (.json .png .jpg .jpeg .webp .gif .bmp .avif .ktx2 .basis .glb .gltf .bin .mp3 .ogg .wav .txt .csv). |
Convert to a supported format, or if you believe the extension should be supported, that is a product decision, not something to route around — see Manifest Reference. |
unsupported-script-type |
entry.script is rejected. |
The path doesn't end in .js. |
Point entry.script at a .js file. TypeScript source must be compiled to .js before it's referenced here — mod scripts are plain JavaScript, unlike table scripts. |
missing-asset |
Registration/re-scan fails fetching a declared asset. |
A HEAD request to raw.githubusercontent.com/<owner>/<repo>/<ref>/<path> didn't return 2xx — the file isn't at that path on that ref, the repo is private, or GitHub is rate-limiting. |
Confirm the file is committed and pushed to the exact ref you registered against; confirm the repo is public; wait and retry if it looks like a transient GitHub error. |
asset-too-large |
An asset is rejected regardless of type. |
The file is over 50 MB. |
Compress or re-export the asset under 50 MB — for textures, consider .ktx2/.basis compressed output from the editor's own Basis pipeline. |
asset-content-type-missing |
An otherwise-valid asset is rejected. |
GitHub (or your upload) didn't return a content-type header for a file whose extension has a known policy. |
Usually a GitHub raw-content quirk; re-push the file. For direct uploads, make sure the browser is sending a real MIME type for the file. |
asset-content-type-mismatch |
An asset is rejected with the actual and expected content-types named in the message. |
The extension and the actual content don't match the policy (e.g. a .png that's actually a renamed .psd). |
Re-export the file in the format its extension claims. Don't just rename a file to get past the check — the message names the mismatch for a reason. |
asset-read-failed |
The HEAD check passed but the scan still fails on the same asset. |
The follow-up GET (used to hash the file) failed after the HEAD succeeded — usually a transient GitHub condition. |
Retry the scan/registration. If persistent, check the file isn't unusually large or the repo isn't being rate-limited. |
| Code |
Symptom |
Cause |
Fix |
media-unsupported-type |
A file under media/ is rejected. |
Its extension isn't .webp, .png, .jpg, or .jpeg. The media/ folder is reserved for cover/screenshot images only. |
Move non-image files out of media/; export cover/screenshot art as one of the four supported image formats. |
media-path-invalid |
manifest.coverImage or a manifest.screenshots[].path is rejected. |
The path isn't a safe relative path under media/ (e.g. it's absolute, traverses .., or is outside media/ entirely). |
Point coverImage/screenshots[].path at a relative path starting with media/. |
Capability rules#
| Code |
Symptom |
Cause |
Fix |
undeclared-capability |
Your script is rejected naming a specific capability, e.g. "read-world". |
Your script calls an api method gated by a capability (see the Capabilities Reference detector table) that isn't listed in manifest.capabilities.allowed. |
Add the named capability to manifest.capabilities.allowed. Only using an undeclared one fails the mod; declaring one you never use is a warning (scene-script-capability-unused below), not a refusal. |
Scene scripts and prefab scripts#
undeclared-capability reads your entry.script only. The three rules below cover the scripts that
live inside setup.json — the scene scripts a game pack ships, and the prefab scripts an Asset
pack (component-pack) ships. They exist because capabilities.allowed is the text every player is
shown before loading your mod, so a capability it omits would run on their table with no disclosure
at all.
| Code |
Symptom |
Cause |
Fix |
scene-script-undeclared-capability |
Your mod comes back incompatible naming a scene script and one capability, e.g. setup.json#scripts/spy.ts / "read-hidden-information". Already-published mods can start failing here. |
A scene script calls an api method gated by a capability that is not in manifest.capabilities.allowed. Every mod type except component-pack. |
Add the named capability to capabilities.allowed in diceytable.mod.json and republish, or remove the call. Those are the only two fixes — the message names both. One issue is reported per undeclared capability per script, so fix them together and re-scan once. |
scene-script-capability-unused |
A warning on diceytable.mod.json; your mod still publishes. |
capabilities.allowed names a capability that neither your entry script nor any scene script appears to use. log is exempt. |
Trim the entry, or ignore the warning if the call is one the static detector cannot see — capability detection is a whole-word pattern match, not a type checker, which is exactly why this is a warning and its undeclared sibling is an error. The cost of leaving it: an unused read-hidden-information puts a consent prompt in front of every player for nothing. For plugin-call: a lobby deckDatabase counts as a use; otherwise remove it together with each plugin's functions list ("functions": []), because listed functions without plugin-call are refused. |
component-pack-undeclared-capability |
An Asset pack is refused naming a prefab script and a capability. Also refused at load time, not just at publish. |
A script carried in the pack's setup.json uses a capability the manifest does not declare. |
Add it to capabilities.allowed and republish, or remove the call. Every room that depends on your pack discloses your declaration to its players, so this one is read by people who cannot see your code. |
Asset pack declarations (component-pack)#
| Code |
Symptom |
Cause |
Fix |
component-pack-capabilities-undeclared |
An Asset pack that ships code is refused before any script is even read. |
The raw diceytable.mod.json has no capabilities.allowed array at all, or an empty one. The schema's ["log"] default does not count — it is the schema's opinion, and it gets republished to every dependent room as though you had asserted it. |
Write the list out explicitly. "allowed": ["log"] satisfies the rule; it is about writing the list, not about how short it is. A pack that ships no code is untouched. |
component-pack-capability-unused |
A warning; the pack still publishes. |
capabilities.allowed names a capability no script in the pack appears to use. log is exempt. |
Trim it, or ignore if the detector cannot see your call. Same asymmetry as scene-script-capability-unused. |
component-pack-script-not-compiled |
A warning naming a prefab script. |
The script has no compiled body, so it will not run and its capability use cannot be checked against your declaration. |
Open it in the code editor and save — that triggers transpilation. |
component-pack-script-compiled-scan |
A warning that always accompanies a content refusal (dom-access, network-access, …) on a prefab script. |
The safety scan reads the compiled body and comments survive the TypeScript emit, so a bare window, top, parent, fetch or eval in a comment is refused exactly as a real call would be. |
If you did not write the call, reword the comment. This warning is never the refusal — fix the content code it came with. |
JSON & schema rules#
| Code |
Symptom |
Cause |
Fix |
invalid-json |
Your manifest or setup file is rejected with a raw parse error, or "Setup JSON could not be parsed." / "JSON could not be parsed." |
The file isn't valid JSON, or (for entry.setup at GitHub-scan time) it parsed but matched neither the edit-scene nor the mod-setup schema. |
Validate the JSON syntax first (a trailing comma is the most common cause). If it's valid JSON but still rejected, confirm it has either both schemaVersion and environment keys (edit-scene) or neither (mod-setup) — a partial match falls through to this error. |
invalid-model-meta |
A <model>.meta.json sidecar is rejected. |
Not valid JSON, or doesn't match the model-meta schema (the message names the offending field). |
Fix the named field, or regenerate the sidecar from the editor rather than hand-editing it. |
model-meta-authoring-ignored |
A warning on a <model>.meta.json sidecar; the mod still publishes. |
The sidecar's collider/triggers block does not match the schema. It will be ignored at runtime — the model still loads, with an automatic collider and no trigger volumes. |
Re-save the model in the model editor rather than hand-editing the sidecar. If the whole file is malformed you get invalid-model-meta (an error) instead. |
invalid-deck-definition |
An assets/decks/<slug>.deck.json file is rejected. |
Not valid JSON, or doesn't match the custom-deck schema (the message names the offending field, e.g. an out-of-range faceIndex). |
Fix the named field, or re-author the deck in the item editor. |
engine-incompatible |
Registration fails naming your compatibility.engine and the current engine version. |
Your declared engine range doesn't include the running engine version. |
See Manifest — Compatibility for the real (non-semver) grammar before writing a range by hand — a malformed range can silently match everything, which is its own footgun in the other direction. |
missing-manifest |
Your local draft won't validate at all. |
The draft's manifest text is empty. |
Open the manifest file in Edit Mode and give it content — every mod needs diceytable.mod.json. |
missing-local-asset |
A draft-only error naming an asset path. |
manifest.assets[] references a path not present among the draft's known files. |
Upload the missing asset, or remove the stale reference from manifest.assets. |
missing-local-setup |
A draft-only error naming entry.setup. |
manifest.entry.setup points at a file the draft doesn't have. |
Create the file at that path, or update entry.setup to match where your setup file actually lives. |
missing-local-script |
A draft-only error naming entry.script. |
manifest.entry.script points at a file the draft doesn't have. |
Create the file at that path, or update entry.script. |
template-instantiation-failed |
A mod-setup (legacy format) file fails with a template error. |
The setup document parses, but expanding its object templates threw — usually a bad template reference. |
Fix the referenced template, or migrate to the modern edit-scene format — see Setup JSON. |
unsafe-project-path |
A draft-only error naming one of your own project files. |
A file's recorded path fails the same relative-path safety check as manifest paths. |
Rename/move the file to a plain relative path with no .., no leading /, no backslashes. |
duplicate-project-path |
A draft-only error naming a path twice. |
Two file-metadata entries share the same path. |
This shouldn't happen through normal Edit Mode use; if you see it, remove the duplicate file entry (or file a bug — see escalations on the reference page). |
schema-* |
A draft-only error like schema-invalid_type or schema-too_small, with a Zod message. |
Your manifest or edit-scene setup document has a field that fails its Zod schema — wrong type, out of range, or a missing required field. |
Read the message — it's the exact Zod validation failure — and fix the named field against Manifest Reference or the setup schema. |
Pack refusals (room packs and table packs)#
A room pack and a table pack carry no executable code and describe only their own subject. Both
rules fire on the presence of a roomPack/tablePack key in your setup.json — the document
does not have to be a valid, or even scene-shaped, pack, and an empty "scripts": [] still counts.
| Code |
Symptom |
Cause |
Fix |
room-pack-scripts-forbidden |
A setup.json carrying roomPack is refused, naming CAP-1. |
The same document also has a scripts or sceneScriptIds key. |
Move the scripts into the game pack that uses this room. Capability disclosure is derived from the room's own mod list, which excludes transitive dependencies, so a script inside a depended-on pack would run with no disclosure firing at all — this is refused rather than stripped for exactly that reason. |
room-pack-forbidden-key |
Same document, refused naming a specific key. |
The roomPack document also carries prefabs, zones, seatZones, seatTemplate, snapPoints, tablePack, tableMetrics or packRefs. |
Move gameplay layout (prefabs, zones, seats, snap points) into the game pack. Do not describe the table: it owns the play-surface height and footprint, and a room may only read them. Depend on another pack in the manifest, never by assigning it here. |
table-pack-scripts-forbidden |
A setup.json carrying tablePack is refused, naming CAP-1. |
The same document also has a scripts or sceneScriptIds key. |
Same fix as the room's: scripts belong to the game pack. |
table-pack-forbidden-key |
Same document, refused naming a specific key. |
The tablePack document also carries room, prefabs, zones, snapPoints or packRefs. |
Remove them. A table describes a table; the room comes from the game's assigned room pack. Unlike a room pack, seatZones and seatTemplate are allowed here — the seat ring is a property of the table's footprint. |
Cost rules, not safety rules. They run only for a setup.json that carries a roomPack key, and
the same numbers are shown live in the Room Editor while you build, so a refusal is never the first
time you see them.
| Code |
Symptom |
Cause |
Fix |
room-budget-exceeded |
Publish refused with a measured number and a limit. |
More than 24 shadow views, 256 draw calls, 1,500,000 triangles, or 256 MB of encoded texture bytes. |
Shadow views, not light count, are what a room costs — a shadow-casting point light renders six views of every caster, a spot or directional renders one. Turn castShadows off on decorative lights first; that is almost always the cheapest fix. Light count itself has no refusal threshold. |
room-budget-warning |
A warning; the mod still publishes. |
Above the recommended value (8 shadow views, 96 draw calls, 250,000 triangles, 96 MB of texture) but below the refusal threshold. |
Nothing is required. Treat it as a note about weaker GPUs, and check the same numbers in the Room Editor. |
room-budget-unmeasurable |
A warning saying the budget could not be measured. |
The document declares roomPack data but does not validate as a room pack. |
Fix whatever makes it invalid — usually a malformed decor placement. Look for a room-pack-* refusal in the same scan first: a forbidden key is the most common reason a room pack fails to parse. |
Cost rules, not safety rules. They run only for a setup.json that carries a componentPack key,
and the same numbers are shown live in the editor while you build.
Before you optimise: rebuilding a deck as a sprite sheet will not make it faster. 300 cards
measured 2,784 draw calls whether they came from one sheet or 300 separate images. A sheet saves
HTTP fetches and texture objects, not frame time. What costs frame time is the number of distinct
card faces, because each one is a distinct material — a card's UV window lives on its material,
which is why a sheet-based card never shared one to begin with.
| Code |
Symptom |
Cause |
Fix |
component-pack-budget-exceeded |
Publish refused with a measured number and a limit. |
A sprite sheet over 8,192px on a side, or more than 4,096 draw calls, 1,500,000 triangles or 256 MB of texture bytes. |
The sheet size is the one you will actually hit. A sheet above the GPU's MAX_TEXTURE_SIZE never uploads and every card in the deck renders with no art at all. Re-generate the deck in the Deck tab — the in-app builder caps itself at 4,096px and will split the cards across sheets or ask you to lower the per-card resolution. A hand-written or imported .deck.json is the only way to get a sheet this large. |
component-pack-budget-warning |
A warning; the mod still publishes. |
Above a recommended value: 200 prefabs, 300 deck cards, 256 distinct card faces, a 4,096px sheet, 1,200 draw calls, 250,000 triangles or 96 MB of texture. |
Nothing is required. deckCards in particular is the dealt cost, not the shipped one — a 300-card deck is 6 draw calls sitting on the table and 2,448 once it is dealt out, and that is the game's choice rather than yours. Treat it as a note about weaker GPUs. |
component-pack-budget-unmeasurable |
A warning saying the budget could not be measured. |
The document declares componentPack data but does not validate as an Asset pack. |
Fix whatever makes it invalid. Look for a component-pack-* refusal in the same scan first — an orphan script, a duplicate prefab id or a deck reference the pack does not ship are the usual causes. |
Dependency graph#
Every one of these needs the registry, so they are reported after the scan, on the mod record,
under a dependency- prefix. An error here makes the mod incompatible, which also means it gets
no published version row — so nobody can depend on it until the graph is clean.
| Code |
Symptom |
Cause |
Fix |
dependency-cycle |
An error naming a chain like a -> b -> a. |
A pack in your graph reaches itself. |
Break the loop: one of the two packs has to stop depending on the other. Extract the shared part into a third pack if they genuinely need each other. |
dependency-depth-exceeded |
An error naming a chain three levels long. |
Transitive dependencies are capped at depth 2 (O-24). |
Depend on the deep pack directly — that flattens it to depth 1 — or vendor its contents into yours. |
dependency-unresolved |
An error naming a pack id and version. |
Nothing published matches that exact (packId, version). The pack may never have been published at that label, may have been deleted, or may have been blocked by an administrator. Matching is exact — no case folding, no near-miss. |
Check the id and the label against the pack's listing, character for character. If it was published without a resolvable commit sha, it has no version row and cannot be depended on until it is re-scanned. |
dependency-version-conflict |
An error naming one pack and two versions, with the chain that pinned each. |
Two places in your graph pin the same pack to different versions. |
Pick one and align. There is no resolver and no "nearest wins": either move your own pin, or ask the other pack to move theirs. |
dependency-self-dependency |
An error saying a pack cannot depend on itself. |
A manifest lists its own id in dependencies. |
Remove the entry. (Your own manifest is refused earlier, at parse — seeing this code means a pack you depend on declares it.) |
dependency-duplicate-dependency |
An error naming a pack listed twice. |
The same pack id appears more than once in one dependencies array. |
Delete the duplicate and keep the version you want. Same caveat: your own manifest is refused at parse. |
dependency-commit-mismatch |
An error naming two commit shas for one version. |
Your pin supplies a commitSha that is not the commit that version was published at. |
Drop the commitSha and let the label resolve — the label is the pin. If you specifically want that commit, pin the version label that actually carries it. |
Publish refusals (HTTP 409)#
Not scanner codes: these refuse the publish request itself, so nothing is written and the mod
record is unchanged. You get an HTTP 409 with a named error.
| Error |
Symptom |
Cause |
Fix |
mod-type-immutable |
Publish fails with 409; the message names what the mod was first published as. |
You changed manifest.type after the mod had already been published. A pack's type is immutable (O-26) — dependents pin a version, not a type, so a flip would change what they load and what capabilities they inherit, with no version bump and no disclosure. |
Publish the new kind under a new mod id. There is no override, and re-registering will not clear it. |
mod-version-immutable |
Publish fails with 409 naming the version and the commit it is already published at. |
You re-published an existing version label from a different commit. A published label is a permanent name for one commit (O-20). |
Bump version in the manifest and publish again. Re-publishing the same label at the same commit is fine and does nothing — this only fires when the commit differs. |
Warning#
| Code |
Symptom |
Cause |
Fix |
scene-script-not-compiled |
A warning, not a rejection by itself — your mod can still be compatible. |
An edit-scene scene script has no compiled body yet. |
Open the script in the code editor and save it — that triggers transpilation. Until then, the script is silently skipped at runtime. |
unstated-model-collider |
Your pieces are placed correctly in the editor but scatter across the room at a real table. |
An entity uses a custom model but has no saved collider, so until its GLB downloads it wears a placeholder box the size of its scale. |
In the editor, select Entities and run Scene Check — one button measures every model and saves its real collider. |
colliders-overlap |
Same symptom: correct in Edit Mode, pushed apart the instant the table runs. |
Two unlocked entities' colliders interpenetrate where they are placed. Edit Mode does not simulate, so nothing separates them there. |
Fix the colliders first — most overlaps are unstated-model-collider in disguise. If they persist with real colliders, move the entities apart or lock the one that is meant to hold the others. |
Hard throws#
These aren't per-field issues — they stop the whole scan/load. There's no code to search
for; match on the message text.
| Message |
Cause |
Fix |
A raw Zod message, returned as mod-scan-failed with HTTP 400. |
Your diceytable.mod.json doesn't satisfy the manifest schema. Four refusals land here that are easy to mistake for something else: a dependency pinned with a range (^1.2.0, ~1.2, 1.x, >=1.0.0) instead of an exact label; more than 24 dependencies; a self-reference or a duplicate pin in your own dependencies; and a room-pack/table-pack that declares an entry.script or any capability beyond log. |
Fix the named field. Pins are exact labels only — there are no version ranges, deliberately, because a range would let an upstream publish change your game and would make a table non-reproducible between two peers who resolved it at different times. See manifest.dependencies. |
| "Use a public GitHub repository URL such as https://github.com/owner/repo." |
The repo URL you registered doesn't match https://github.com/<owner>/<repo>. |
Use the plain repo URL, not a URL to a specific file, branch view, or a non-GitHub host. |
| "Unable to fetch <url>: <status> <statusText>" |
Registration/re-scan couldn't fetch the manifest, script, or a setup file from raw.githubusercontent.com. |
Confirm the repo is public and the ref exists. A 404 almost always means a wrong path or ref; other statuses are usually transient — retry. |
| "Only compatible mods can be loaded." |
Something tried to load a mod whose stored scan status isn't compatible. |
Fix whatever rule made the last scan incompatible (see the rest of this page), then re-register or re-scan. |
| "Mod scene scripts no longer pass sandbox compatibility checks." |
The mod passed registration, but at load time its edit-scene scripts fail the same scan again. |
See the serve-time re-scan risk — either your repo changed since registration, or a scanner rule changed. Re-scan to see the current issue codes. |
| "Mod script no longer passes sandbox compatibility checks." |
Same as above, for entry.script instead of scene scripts. |
Same fix — re-scan and read the current issues; consider pinning ref to an immutable commit sha. |
| "Mod scene scripts no longer pass manifest capability checks." |
Not a content problem. At load time a scene script was found using a capability your manifest.capabilities.allowed does not declare — scene-script-undeclared-capability, enforced again at serve time. |
Re-scan to see which script and which capability. Then add that capability to capabilities.allowed in diceytable.mod.json and republish, or remove the call. Read the message carefully first: manifest capability means your declaration is wrong, sandbox compatibility (the two rows above) means your code is. |
| "Mod prefab scripts no longer pass component-pack capability checks." |
An Asset pack's prefab script fails component-pack-undeclared-capability (or a banned content pattern) at load time. |
Same fix, on the pack: declare the capability and republish, or remove the call. |
The editor upload gate#
| Symptom |
Cause |
Fix |
Asset upload failed for "<path>" (unsupported-asset-type): Unsupported asset type: <ext>. Allowed: <list>. |
You tried to upload a file whose extension has no content-type policy at all (e.g. .exe, .html, .psd). |
Convert to one of the 18 supported extensions before uploading, or don't upload that file — the editor's file explorer isn't a general file host. |
Asset upload failed for "<path>" (asset-content-type-*): … |
Same content-type checks as the GitHub scan, applied to the browser-supplied contentType at upload time. |
Re-export the file so its content matches its extension. |
| "model assets must be 25 MB or smaller" / "assets must be 50 MB or smaller in v1" |
Ad hoc upload-time size caps in store.ts — not one of the 66 rules on the reference page, but enforced at the same moment. |
Compress the model/asset below the limit. |
See also#