Skip to content

Troubleshooting

Symptom-first fixes for tCAD: a blank panel, nothing opening, every action denied, nothing persisting, a backend that will not bind, strings showing as keys, and the failure modes that are by design.

Run the diagnostics before changing anything. All of them are server console commands and they ignore a player caller.

tcad_config        every config key with its resolved value
tcad_hooks         the eight hooks, and whether each signature is as expected
tcad_selftest      35 assertions against the live backend, reported per case
tcad_writeprobe    proves a write to data.json actually lands
tcad_sqlprobe      which database exports resolve, and through which resource
tcad_exports       what the export surface published, and the 911 counters

Most problems below are one of four things: the build was not run, a grant is missing, a backend had not bound yet, or the behaviour is by design.


Nothing happens when I press the key or type /tcad

Check
Is tcad.use granted to this player's group?Without it, the CAD does not open. tcad_config shows the key Config.acePermission resolves to
Did canOpenCAD refuse?If your hook returned false, its message is shown as a notification. tcad_hooks reports whether the hook loaded
Is the keybind bound?Config.openKey = '' ships unbound on purpose. The row is in GTA's Settings → Key Bindings for the player to assign. /tcad always works regardless
Did tCAD start at all?tLib must ensure before tCAD

The panel opens to a blank screen

The UI bundle was not built. client/ui/ is the ui_page and it is not in version control.

bash
bun install
bun run build

A release archive ships it already built; a git clone does not, and the failure is silent — a cursor over nothing.

Every action is denied

Expected on a fresh install. Nothing is granted by default: tLib's permission adapter passes each key straight to IsPlayerAceAllowed, there is no registry and no implicit admin bypass, so an ungranted key denies everyone.

cfg
add_ace group.admin "tcad" allow

ACE inheritance applies and every key shares the tcad. prefix, so that grants the whole tree. Permissions has a per-role block.

If a specific action is denied and others are not, read the error: it names the ACE key. The key is not a secret — it is in config.lua.

If denyMethod is refusing, you will see it refuse with your own message. And note the one deliberate exception in the hook set: if denyMethod raises, the request is refused, so a broken hook refuses every operation for every player, with the hook named in the console each time. That is loud on purpose — the alternative is a restriction that silently stopped applying.

Nothing is being saved

⛔ The failure mode worth knowing about before it happens. On the JSON backend, if the server bundle is missing, the CAD works perfectly for a whole shift and persists nothing.

tcad_writeprobe

If it reports a write fault, the build step was not run:

bash
bun run build      # produces server/dist/bundle.js

A green self-test is not evidence of persistence. The first FXServer run of this resource reported 35 passed, 0 failed while nothing was being written at all. That is why tcad_writeprobe is a separate command rather than a line in the self-test — assert durability separately from behaviour.

Everything answers "still starting up"

The storage backend has not bound yet. This is a real state, not an error.

A database resource becomes able to answer asynchronously — measured, about 2.5 seconds after it reads as started. So tCAD probes a real export and waits, and every operation answers starting until a backend is committed.

If it never stops:

tcad_sqlprobe

reports which export names resolve and through which resource. Two things that look like this and are not:

  • provide 'oxmysql' does not alias the export namespace. It satisfies a dependency and sets the resource state, and nothing more — measured, all 22 export names resolve via the providing resource's own namespace and none via oxmysql's. tCAD resolves the host at runtime for that reason, so this is handled, but it is why a probe is the answer and not a guess.
  • A stopped database folder still reads as present under a ~= 'missing' test. tCAD compares against 'started' specifically so a database resource you stopped months ago cannot knock out the live one. If detection picks the wrong one, pin it: Config.storage = 'tdb' or 'oxmysql' or 'json'.

The CAD seeded the wrong world, or reseeding does nothing

Seeding only ever happens into empty tables. Changing Config.seed on a server that has already booted does nothing — the gate is emptiness, so a staff member's edit to a label survives every restart.

tcad_reseed

re-runs it. ⛔ This is the most destructive command in the resource on a live server, which is why it is console-only.

If the civilian, business or judicial views are unreachable, you are on Config.seed = 'minimal': those three agency types exist only in the full seed, and an agency's agencyType is what selects the view. See Configuration.

A label on screen reads detail.timeline or calls.something

A string key is not in the table. That rendering is deliberate — a literal key on screen is an unmissable "this is missing", where a blank label is one nobody reports.

Either a typo in your locales.json, or you are overriding a key that no longer exists. tcad_config reports the string count and section names. See Updating.

If a reword you made has reverted, you edited shared/locales.default.json. That file is replaced on every update. Your overrides go in locales.json.

A placeholder carries a {placeholder} that was never filled

You renamed a placeholder while rewording the string. The names are what the code fills — reword the sentence around them, keep the names.

Escape does not close a dialog, and the keyboard does not work in a menu

By design, because of a measured platform limitation. In the Chromium version FiveM's CEF actually is:

  • Neither Escape nor an outside click dismisses any overlay. Every overlay therefore carries a visible close button, and nothing in the CAD relies on either gesture. Escape is owned by the NUI itself and closes the whole panel.
  • Focus never enters an opened overlay, so keyboard selection in a menu does not work — Enter keeps hitting the trigger. Pointer selection works, and the combobox and select keyboard paths do work.

Arguably better for a CAD: a half-written call entry is not lost to a stray keypress.

The roster shows a unit whose player has left

Deliberate. A duty session is not ended by a disconnect, so the row is kept, not filtered — a dispatcher is still looking at it, and a unit that vanished mid-call is worse than one marked stale.

Names are resolved from the live player list on every read, so they are never stale; a unit whose player has gone keeps the stored name rather than blanking.

The audit log is empty but the catalogues are full

Correct, and the screen says so. The first-boot seed writes no audit row, deliberately — that world came from a config value, not from a person. There is nothing to attribute.

The screen never renders an "unknown actor" row for data nobody did.

NCIC returns nothing for somebody I know is wanted

Read the answer, not its emptiness. "No records found" and "you may not run this query" are different answers, and NCIC shows the refusal in the server's own words for exactly this reason. Without tcad.ncic.run a refusal is the normal outcome.

If the result is genuinely empty, the record may be outside the search fields — the search is an allowlist, not every column.

A person's medical history looks blank

It is withheld from the NCIC result, and the screen says so rather than leaving a gap. An officer must not read an empty section as "this person has no medical history".

On the civilian Home tab, a character's own medical note is behind a labelled reveal and collapsed by default — not for authority reasons but because a medical note that is on screen by default is one that ends up in a clip.

A warrant shows the subject's identifying number in full

Deliberate, and gated: it needs tcad.civilian.view.

A warrant is otherwise readable by anyone with tcad.use — it is a public court record — but that field is not. If you grant tcad.warrants.approve or tcad.warrants.deny to a group, grant tcad.civilian.view to the same group, or a judge cannot tell two identically-named subjects apart, which is a worse outcome than the disclosure.

A record I own reads as "does not exist"

Deliberate, and it is the correct behaviour. A record outside your scope reads as absent, not as refused — "that record is not yours" confirms it exists, which is a disclosure in itself.

If it really is yours and reads as absent, it is keyed to a different licence identifier. That happens if the player's identifiers changed.

Attaching a unit answers ambiguous

The unit is on duty in more than one department and no department was named. It is refused rather than guessed, because guessing would attach a different unit and return a correct-looking success — and attach is idempotent, so nothing downstream would notice.

Pass a departmentId. getDepartments is the list.

A tab says it is waiting for something

It is one of the 24 placeholders. Each names the server operation it needs, with the payload and permission that operation should carry. Status is the full inventory.

No such export from my own resource

The export surface refused to register anything at all. That happens on a failed structural check, and it is louder and more accurate than an export that exists and answers internal for ever.

tcad_exports

reports what was published and what was refused. The usual cause is a load-order change: the surface must register after the operation registry, which publishes what it reads at file scope.

My 911 integration stopped working

tcad_exports

The counters are there for exactly this: a consumer that is being rate limited looks identical to one that was never wired up. The limits are 10 seconds per player and 30 per minute globally — the global cap being the one that matters, since any resource on the box can reach it and a per-player cooldown does nothing against a loop over every slot.

If it answers disabled, 911 was switched off: tcad_exports 911 on, or Config.exports.allow911 = true.

I cannot find an export for civilians, warrants or NCIC

⛔ There is none, deliberately, and there will not be. An export has no source, so it has no permission boundary — a getCivilian would hand a subject's medical notes to any resource on the box and defeat tcad.civilian.view entirely.

The surface refuses to register at all if it ever reaches one of those operations, and that is checked at boot. If a question genuinely needs record data to answer, the answer is narrowed to the question — which is what getUnitNote does — never widened to the record. See API reference.

My config change did nothing

config.lua is read at resource start. Restart tCAD, then:

tcad_config

A typo that happens to be valid Lua is otherwise invisible until the feature it governs misbehaves. An unrecognised value for a keyed option is reported by name with the legal set — it is not a silent fallback.

Something is wrong and none of the above fits

tcad_selftest

35 assertions against the live backend, reported per case, and it names the backend it resolved. It is server-side evidence only — a console command has no player, so NUI rendering, the client script and the client→server hop are untouched by it.

Status is explicit about what has and has not been exercised, and is the right page to check before concluding you have found a bug in something that has run.

Was this page helpful?