> For the complete documentation index, see [llms.txt](https://tyrix.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tyrix.gitbook.io/docs/script-resources/tyrix-multi-job/troubleshooting.md).

# Troubleshooting

Almost every problem with this resource falls into one of two buckets: either the resource never got the data it needs (framework, database, player load), or a server-side permission check is refusing an action and the UI has nothing to show you. This page is organised by symptom. Find yours, read the diagnosis, work the fix steps in order.

## Read the console first

Two consoles matter, and they show different things. The **server console** (txAdmin live console, or the server window) carries everything the resource prints about the database, jobs and the weekly reset. The **client console** — F8 in game — carries client errors, missing-export errors and the player-load failure.

A healthy start prints exactly **one** line, and it is the most useful line in the resource:

```
[tyrix_multijob] ready | framework esx | locale en | 24 jobs | 6 tables | hours on | discord off
```

Read it left to right and you have confirmed the framework was detected, the locale file loaded, the jobs cache was built, the schema installed, and whether hour tracking and Discord logging are on. If that line never appears, boot did not finish — something above it in the console will say why.

Everything else is printed only when it matters:

| Console line                                             | Meaning                                                                                  |
| -------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `weekly reset \| N rows rolled into week YYYY-MM-DD`     | The weekly sweep ran and changed something. The date is the week start currently in use. |
| `funds locked - server restart imminent`                 | The pre-restart funds lock engaged.                                                      |
| `no jobs found in your framework's jobs table …`         | The jobs cache is empty. Fix this first — see below.                                     |
| `locale X could not be loaded - falling back to English` | Your `Config.Locale` file is missing or has a JSON error.                                |
| `ignoring malformed Config.maxJobsOverrides[…]`          | An override entry is not a whole number or `false`.                                      |
| `schema statement failed: …`                             | One `CREATE TABLE` was rejected. The MySQL error follows.                                |
| `SQL.sql not found - skipping schema install`            | The resource files are incomplete.                                                       |
| `no supported framework detected …`                      | Neither es\_extended, qb-core nor qbx\_core is started.                                  |
| `failed to load your job data …`                         | Client-side (F8). That player never received their job list.                             |

Prefix colour tells you severity at a glance: green is success, blue is informational, yellow is a misconfiguration the resource worked around, red is broken.

{% hint style="warning" %}
**If `0 jobs` appears, stop and fix that first.** Jobs and grades come from your framework's `jobs` and `job_grades` tables, not from this resource — with an empty jobs cache nothing else can work, because every save, clock-in and boss action validates against it.
{% endhint %}

## Turning on verbose logging

The resource is deliberately quiet. When you need to see what it is actually doing — every hour save, every weekly reset, bridge initialisation, missing locale keys — turn on debug output in `shared/config.lua`:

```lua
Config.Debug = true
```

Restart, and those lines appear in grey alongside the normal output. Leave it off in production; the periodic hour saver alone will print on every interval.

## The jobs menu will not open

Work through this in order, because each step rules out the one before it.

{% stepper %}
{% step %}

## Type the command in chat directly

The default is `/jobs`, or whatever you set `Config.openJobMenu.command` to. If the command works but your key does not, it is a keybind problem. If neither works, the resource is not registering anything.
{% endstep %}

{% step %}

## Check the feature is on

With `enabled = false` the resource registers neither the command nor the keybind — there is nothing to press and nothing to type.

```lua
Config.openJobMenu = {
    enabled = true,
    command = 'jobs',
    key = 'F5',
}
```

{% endstep %}

{% step %}

## Check for a command collision

The command is registered with the plain name you configure. If another resource on your server already registers `jobs`, one of the two handlers wins and it may not be this one. Rename `command` to something unmistakably yours, restart, and try again.

{% hint style="info" %}
Renaming `command` also renames the keybind, because the keybind is mapped to the command. Players who already bound the old command will need to bind the new one.
{% endhint %}
{% endstep %}

{% step %}

## Check the keybind

The key is registered as a FiveM key mapping, which means the value in the config is only the *default* suggestion. Once a player has connected, their binding lives in their own game settings, and changing `key` in the config will not move it.

Send them to **Settings → Key Bindings → FiveM**, find the entry labelled `Open job menu`, and bind it there. Set `key = false` if you would rather ship no default binding at all and drive everything through the command.
{% endstep %}

{% step %}

## Check F8 for the load failure

The menu is built from a job list the client fetches once when the player loads. If you see `failed to load your job data` in F8, the client asked and the server did not answer within ten seconds — usually because the database was still connecting, the framework had not finished starting, or the server was refusing queries.

Have the player rejoin. If it repeats for everyone, treat it as a database or load-order problem and see the SQL section below.
{% endstep %}

{% step %}

## Check whether the menu is simply empty

An empty menu is not a broken menu. A player with no saved jobs opens the menu to an empty list. That is correct: jobs are recorded when your framework assigns one, so a brand-new character has nothing until they are hired.
{% endstep %}
{% endstepper %}

## The boss menu prompt does not appear

The **boss menu** interaction is only created for locations that have coordinates, and the prompt itself is gated on two conditions checked live: the player's *current* job must equal the location's `job`, and their grade's name must be a boss grade. If either fails you get no prompt, no text and no error — by design, so non-bosses cannot see that a boss point exists.

{% stepper %}
{% step %}

## The player must be clocked into that job

Holding a job in the multijob list is not enough. The check compares against the player's active framework job, so a police officer who is currently clocked into their mechanic job gets nothing at the police boss point.

Have them open the jobs menu and clock into the job first.
{% endstep %}

{% step %}

## Check the grade name in `bossGrades`

`Config.bossMenus.bossGrades` is keyed on the value of the `name` column in your framework's `job_grades` table — not the `label` your players see, and not the numeric grade.

Look yours up:

```sql
SELECT job_name, grade, name, label FROM job_grades WHERE job_name = 'police' ORDER BY grade;
```

Given a result where the top rank is `grade = 3`, `name = 'boss'`, `label = 'Chief of Police'`, the correct config is:

```lua
bossGrades = {
    ['boss'] = true,
}
```

`['Chief of Police'] = true` will never match, and neither will `[3] = true`. If a job's top rank is named something else — `owner`, `captain`, `ceo` — add that name as its own key. Every job on your server shares this one list, so a job whose boss rank is named `boss` and a job whose boss rank is named `owner` both need their name present.
{% endstep %}

{% step %}

## Check the interaction type

`interactionType` accepts exactly three values:

| Value         | What it creates                                          | Requires                       |
| ------------- | -------------------------------------------------------- | ------------------------------ |
| `'zones'`     | An ox\_lib sphere zone with a text prompt and a keypress | ox\_lib (already a dependency) |
| `'target'`    | An ox\_target sphere zone in the third eye               | `ox_target` started            |
| `'qb-target'` | A qb-target circle zone in the third eye                 | `qb-target` started            |

Any other value — including a near miss like `'ox_target'` — matches none of the three branches, so the resource builds no interaction at all and prints nothing.

If you pick `'target'` or `'qb-target'` and that resource is not started, F8 will show an error about the missing export instead.
{% endstep %}

{% step %}

## Check the right distance setting

`'zones'` mode uses `distance` as the zone radius; both target modes use `targetDistance`. Tuning the wrong one has no effect on the mode you are actually running.
{% endstep %}

{% step %}

## Check for coordinate-less entries

Coordinate-less entries create nothing on purpose. An entry with no `coords` exists only to carry money-wash settings for a job whose menu is opened by another script:

```lua
locations = {
    { coords = vec3(-803.64, 168.12, 72.82), job = 'police', label = 'POLICE', moneywash = true, percent = 0.90 },
    { job = 'mechanic', moneywash = true, percent = 0.85 }, -- config only, no in-world point
}
```

{% endstep %}

{% step %}

## Restart after adding locations

The interaction points are built once, after the player's data loads. Add or move a location and you must `restart tyrix_multijob`. Players who were already in-game when you restarted may not get their points back until they rejoin, because the builder waits on the player-load event.
{% endstep %}
{% endstepper %}

Full detail on each setting is in [Configuration → Boss menu](/docs/script-resources/tyrix-multi-job/configuration.md#boss-menu), and the panel itself is documented in [Boss Menu](/docs/script-resources/tyrix-multi-job/boss-menu.md).

## Boss menu actions silently do nothing

Opening the boss menu proves nothing about whether its actions will be accepted. The server re-verifies **every** action independently, and a failed check returns a plain refusal with no reason attached. Sometimes that means the panel does not change at all — a refused rank change is silent — and sometimes it means a misleading toast: a refused deposit, withdrawal or bonus reports **Lacking Funds** whatever the real cause was.

The checks the server applies, though not every action runs all of them:

| Check           | Refuses when                                                                                                                  |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Boss permission | The caller's current job is not the society being acted on, or their grade name is not in `bossGrades`                        |
| Known society   | The job is not in the jobs cache loaded at start                                                                              |
| Rate limit      | A deposit or withdrawal happened less than 5 seconds ago                                                                      |
| Restart lock    | A txAdmin restart is imminent                                                                                                 |
| Proximity       | `serverSideDistanceCheck` is on and the caller is not near a configured location for that job                                 |
| Amount          | Not a number, zero or below, or above 10,000,000                                                                              |
| Target          | The identifier is malformed or has no employment row for that job; firing also refuses if the target is the caller themselves |

Two of these catch people out.

### Proximity, when you open the menu from somewhere else

With `serverSideDistanceCheck = true` the server requires the caller to be within the zone radius plus two metres of a configured location whose `job` matches the society. If there is no configured location for that job at all, the check fails outright and every action is refused.

That is exactly what happens when an external script — a business creator, a custom boss point, your own menu — opens the panel at coordinates this resource has never heard of. Either leave the setting at its shipped default:

```lua
serverSideDistanceCheck = false,   -- required if anything else opens the boss menu
```

…or add a coordinate-less `locations` entry for that job, which the proximity check treats as always in range.

{% hint style="info" %}
The proximity tolerance is derived from `distance`, the zones-mode radius, even when you are running a target mode. If you raised `targetDistance` and left `distance` small, a boss standing at the far edge of the third-eye zone can be inside the interaction and outside the server's tolerance — actions open fine and then refuse. Keep `distance` at or above `targetDistance` when `serverSideDistanceCheck` is on.
{% endhint %}

### The rate limit and the restart lock

Deposits and withdrawals share one 5-second bucket per player, so alternating between them does not get around it. Separately, fund transfers are blocked for the last 10 minutes before a txAdmin scheduled restart; the server prints `Funds disabled - server restart imminent` and the panel shows the funds controls as locked.

This protects against money vanishing mid-write when the server goes down, and it clears itself the moment the resource starts again. Nothing to fix — wait for the restart.

Salary editing has its own switch: with `salaryManagement` at its shipped default of `false`, the boss menu never draws the salary controls, so there is nothing for a boss to press.

## Society funds show zero or transfers fail

On ESX, society money lives in `esx_addonaccount`, in a shared account named `society_` plus the job name — `society_police` for the `police` job. This resource reads the balance from the `addon_account_data` table and performs the actual movements through esx\_addonaccount itself. Both halves have to be working.

### Start esx\_addonaccount before this resource

If the account cannot be resolved, deposits refund the player's money and return a failure rather than pocketing it, and withdrawals and bonuses refuse outright. Order your `server.cfg` so the dependencies come up first:

```cfg
ensure oxmysql
ensure ox_lib
ensure es_extended
ensure esx_addonaccount
ensure tyrix_multijob
```

### Accounts register themselves on first use

You do not need to pre-create an account for a new job. The first time the resource needs one it inserts the `addon_account` row, announces the society, and asks esx\_addonaccount to reload its registry. A brand-new job therefore shows a zero balance until its first deposit, which is correct rather than broken.

{% hint style="warning" %}
Do not edit `addon_account_data` by hand on a running server. esx\_addonaccount holds society balances in memory and owns that row; a manual `UPDATE` will be overwritten the next time it writes, and you will have handed someone a balance that silently reverts. Move money through the boss menu, which produces a transaction record you can audit.
{% endhint %}

### If the balance looks stale

Remember the read is cached for five seconds and comes from the database, while the write goes through esx\_addonaccount. A successful transfer pushes the new balance to the panel directly, so that figure is the authoritative one. Reopen the menu after a few seconds if you want to confirm from the database side.

### Check the server console

A refused transfer that leaves no console error is a failed permission, proximity or rate-limit check — go back to [Boss menu actions silently do nothing](#boss-menu-actions-silently-do-nothing). A refused transfer accompanied by a MySQL error is a schema problem.

## Tables are missing or SQL errors appear on boot

The schema installs itself. On start the resource reads `SQL.sql` out of its own folder, strips comments, and runs each statement in turn. Every statement is a `CREATE TABLE IF NOT EXISTS`, so this is safe on every restart and does nothing on an established database. Importing `SQL.sql` yourself is optional.

Statements are run individually and failures are caught, so one bad statement does not abort the rest — which means a partially installed schema is possible, and the console is the only place you will see it. Look for `Schema statement failed:` lines; the MySQL error text follows and names the problem directly.

The usual causes:

| Cause                                              | Fix                                                                                                                      |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| The database user has no `CREATE` privilege        | Grant it, or import `SQL.sql` manually with an account that has it                                                       |
| oxmysql was not connected yet                      | `ensure oxmysql` before `ensure tyrix_multijob` in your `server.cfg`                                                     |
| The connection string points at the wrong database | Correct it; a wrong-but-valid database installs the tables in the wrong place, which looks identical to "tables missing" |
| `SQL.sql not found`                                | The resource folder is incomplete. Re-download and replace it                                                            |

`SQL.sql` also contains `addon_account` and `addon_account_data`, so a bare ESX database gets the society tables it needs. Existing tables are left untouched.

The manual fallback is straightforward: open `SQL.sql`, run it against your database in HeidiSQL, phpMyAdmin or the client of your choice, then `restart tyrix_multijob` and confirm the `ready` line reports the expected table count with no `schema statement failed` errors above it. The tables and their columns are listed in [Database](/docs/script-resources/tyrix-multi-job/database.md).

## Hours are not tracking

### Hours tracking is a toggle

With `Config.hoursTracking.enabled = false` no hours are recorded, no goals are shown and the hours columns disappear from the boss menu's employee list.

### Hours accrue only while clocked into the job

A clock-in stamps a start time against that player and job. The time is banked — added to both all-time and weekly totals — when they clock out, or when they clock into a *different* job. Holding a job without clocking into it earns nothing, which is the whole point of the feature. A player who never clocks into a job reads as zero for it.

### The number lags by up to `saveInterval` minutes

A session in progress is not yet in the totals. A background thread banks every in-progress session every `saveInterval` minutes, defaulting to 5, so a player who has been on duty for eight minutes may still read as five.

Lowering the interval tightens that at the cost of more database writes; the interval also decides how much time is at risk if the server crashes rather than shuts down cleanly.

```lua
Config.hoursTracking = {
    enabled = true,
    weeklyResetDay = 1,
    defaultWeeklyGoal = 5.00,
    saveInterval = 5,
}
```

### Weekly hours reset, all-time hours do not

If a player's weekly figure went to zero, that is the reset doing its job — see the next section. A single banked session is capped at 168 hours, so a session left open across a long outage cannot inflate a total beyond one week's worth.

The model, including how per-rank goals are resolved and what `defaultWeeklyGoal` covers, is documented in [Hours Tracking](/docs/script-resources/tyrix-multi-job/hours-tracking.md).

## Weekly hours reset on the wrong day

`weeklyResetDay` is a day number, and the numbering starts at Monday:

| Value | Day       |
| ----- | --------- |
| `1`   | Monday    |
| `2`   | Tuesday   |
| `3`   | Wednesday |
| `4`   | Thursday  |
| `5`   | Friday    |
| `6`   | Saturday  |
| `7`   | Sunday    |

The boundary is midnight in the **server's local time**, taken from the machine clock. It is not UTC and it is not each player's timezone. A server whose operating system is set to a different region than you assume will reset hours at what looks like the wrong hour, and around midnight it will look like the wrong day. Confirm the host's clock and timezone before changing the setting.

To see which week the resource currently thinks it is in, look for `Weekly reset: N rows rolled into week YYYY-MM-DD` in the server console. That date is the computed week start, and it is the ground truth for this setting. The sweep runs once shortly after start — catching up any boundary crossed while the server was offline — and then hourly, so a reset can land up to an hour after midnight rather than exactly on it.

Changing `weeklyResetDay` mid-week is not symmetrical, because the sweep only zeroes rows whose stored week start is strictly *older* than the newly computed one. The week start is always the most recent occurrence of the reset day.

So moving the reset day to one that has **already passed this week** pushes the computed week start forward, every existing row looks stale, and the next sweep zeroes weekly hours. Moving it to a day **still ahead** in the week pulls the computed week start back into the previous week, and rows are re-stamped without being zeroed. If you need to change the day without disrupting a live week, do it on the new reset day itself.

## Discord logs are not arriving

Logging is configured in `shared/config_sv.lua`. Four conditions must all hold before a webhook is sent, and any one of them failing produces silence rather than an error:

| Condition                    | Setting                                                                        |
| ---------------------------- | ------------------------------------------------------------------------------ |
| Logging is on                | `Config_SV.DiscordLogs.enabled = true`                                         |
| A webhook is set             | `Config_SV.DiscordLogs.webhook` is a non-empty URL                             |
| The action's type is on      | The matching key in `Config_SV.DiscordLogs.logTypes`                           |
| The resource loaded the file | `Config_SV` exists at all — a Lua syntax error in the file leaves it undefined |

```lua
Config_SV.DiscordLogs = {
    enabled = true,
    webhook = 'https://discord.com/api/webhooks/…',
}
```

Paste the **whole** webhook URL, including the `https://` and the token segment at the end. A channel link is not a webhook URL.

The request is fire-and-forget: the resource does not inspect Discord's response, so a revoked or mistyped webhook fails without complaint from the resource. The server console is where you will see FiveM's own HTTP errors for that request. Discord also rate-limits webhooks, and nothing is retried, so a burst of boss actions in a few seconds can lose entries.

If some log types arrive and others do not, you are looking at `logTypes`, not at the webhook. Each action type is toggled independently — see [Discord Logging](/docs/script-resources/tyrix-multi-job/discord-logging.md) for the full list and the embed colours.

## The UI looks stale after a config change

`shared/config.lua`, `shared/config_sv.lua` and your locale JSON are read when the resource starts. Nothing re-reads them while it is running, so any edit needs:

```cfg
restart tyrix_multijob
```

That applies to text too, and it applies to **all** of it. Notifications, dialogs and every label the interface draws come from the same locale file, delivered to the interface at runtime — so a translation change needs a restart and nothing more. There is no rebuild step. See [Locales](/docs/script-resources/tyrix-multi-job/locales.md).

If text comes up as raw key paths like `web.jobs.title`, the interface did not receive its locale table. Check the server console for a yellow `[tyrix_multijob]` locale warning — a JSON syntax error in your locale file is the usual cause, and the resource falls back to English rather than starting broken.

The interface itself is prebuilt and ships compiled inside the resource. There is no build step for you to run and no reason to edit anything under `web/`; a restart picks up your Lua-side changes and the panel renders them. If the panel comes up blank rather than stale, that is not a config problem — check F8 for a client error and confirm the resource started cleanly.

Two changes need more than a restart:

* New or moved boss menu `locations` require players already in-game to rejoin before their interaction points exist.
* A job added to your framework's `jobs` table after start is unknown to this resource until it restarts, because the jobs cache is built once at boot.

## Getting support

Before asking, restart the resource once and capture both consoles — most of the answers above are already printed there, and a report without them is a guessing game.

Include all of the following:

| What                           | Why it matters                                                                                                          |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| Framework and version          | ESX, QBCore or QBox, and the exact version. Bridge behaviour differs per framework                                      |
| Resource version               | The version of tyrix\_multijob you are running — see [Change Log](/docs/script-resources/tyrix-multi-job/change-log.md) |
| The exact server console error | Copy the text, not a paraphrase, and include the lines around it                                                        |
| F8 client errors               | Client-side failures never appear in the server console                                                                 |
| The relevant config block      | The section you changed, pasted as Lua rather than described                                                            |
| What you changed last          | Almost every "it stopped working" traces back to the previous edit, another resource added, or a framework update       |
| Whether it ever worked         | A never-worked problem is usually setup; a stopped-working problem is usually a change                                  |

If the problem involves boss permissions, also include the output of the `job_grades` query from the boss menu section for the affected job — the `name` column is the answer more often than not.

Ask in the channel where you purchased the resource, so your purchase can be verified and the reply reaches you.
