FAQ
Symptoms whose cause lives on another page: find the symptom, then follow the link. A trap that stays within one page is in that page’s Gotchas table.
First steps when something is wrong
| Step | Command | Tells you |
|---|---|---|
| 1 | mantle check | Syntax and top-level errors, with file and line. Node and layout errors as laid out with every capability nil, then with sample data (what check covers) |
| 2 | mantle log | Evaluation errors, layout errors, errors raised in callbacks, failed mantle calls and refused mantle set/toggle writes (output and logging) |
| 3 | MANTLE_DUMP_LAYOUT=<id>@<output> mantle -vvv | Every visible node’s kind and rect on that surface after each pass (how do I) |
Nothing shows
| Symptom | Cause | Fix |
|---|---|---|
| No surface appears at all after starting | The startup evaluation raised, so there is no scene. The error is in the log | Evaluation, reload and generations |
mantle check passes, but a surface is empty or lays out wrong | check lays out on a 1920x1080 output with every capability nil, then with one sample value each, so a branch that needs a particular value, or a smaller output, went unchecked. The running shell reports those in mantle log | What check covers, then the layout dump and the layout model |
| A node shows before its data arrives | The map returns nil for visible, which counts as absent, and visible defaults to true | signals gotchas |
| Two bars on screen | Two shells are running, one per mantle start | mantle list, then mantle stop --pid <pid> (shell selection) |
| The shell vanishes and comes back only after 30 s | The Renderer died three times within 60 s, so the next respawn waits | Fix the error in mantle log (limits) |
A save or a click does nothing
| Symptom | Cause | Fix |
|---|---|---|
Saving a file leaves the old UI on screen, and mantle.rescue.is_rescue is true | The reload failed: an evaluation or apply error keeps the previous scene, sets mantle.rescue and logs the error. The next reload that applies clears it | Find out why a reload did nothing, error banner |
After a broken save, mantle call says the action does not exist, timers stop and on_change goes quiet | The save raised a Lua error: a failed evaluation drops every action, timer, handler and idle threshold. A save that evaluates but fails to apply keeps its actions and handlers, not its timers | Evaluation, reload and generations |
An on_change, timer, process.run, palette or idle callback does nothing | Its error is a warning, and so is a blown CPU budget in on_change or timer. Read mantle log | Output and logging |
mantle.<cap>:<action>(...) returns nil and nothing changes | Actions are fire and forget. A wrong argument raises at the call, so the Supervisor refused what needs its live state, such as a stale ID, with a log line | Read mantle log; actions |
A keybind running mantle set or mantle toggle does nothing | It may have been refused (an undeclared name, a bare toggle on a non-boolean). The compositor discards the error | Run the command in a terminal or read mantle log (values and arguments) |
Saving a .json or an image beside shell.lua does not reload | Only .lua and .frag changes reload; a byte-identical save and an unreadable directory (changes inside it will not reload) do not either | Evaluation, reload and generations |
An edit to fonts { ... } does nothing | The font chain is read when the Renderer starts | Restart the shell (fonts) |
A textfield shows no caret and takes no keys | The panel does not take keyboard focus | text fields, panel |
Values are wrong or stale
| Symptom | Cause | Fix |
|---|---|---|
A :get() raises attempt to index a nil value at startup, or a capability reads nil | Every capability reads nil until its first push, for mantle check’s first pass, and for good when its backend is missing (lock and polkit push at startup, so they read nil only while a relock is pending); :map and computed skip the nil | The one rule, capabilities |
| A text never updates | It holds a :get() snapshot, not the signal | The one rule |
| A setting is lost after the shell restarts | Named state lives in the Renderer and dies with it | persistent_table |
| A switched view keeps old state, or snaps in without its animation | visible = false freezes the subtree in place; two id-less views of the same kind are reused | Switching views, nodes |
Errors in the log
| Message | Cause | Fix |
|---|---|---|
surface 2 is a string, not a node | require returned the module and its path into the surface list | Modules and require |
exceeded the 50ms CPU limit for one evaluation | A map, computed, handler or timer ran away or did far too much work | Limits and budgets |
signal nesting exceeded its maximum depth of 32 levels | A derived chain reads itself or nests too deep | Errors |
a Signal resolved to another Signal | A map returned a signal | Errors |
`margin.left` is a Signal handle | A signal inside a table a signal returned, or under a metatable, is not read | signals gotchas |
`mantle` asked to write state ... and was refused | mantle set/toggle named an undeclared state, or wrote a value it refuses | Values and arguments |
Running processes
| Symptom | Cause | Fix |
|---|---|---|
process.run prints nothing and on_exit gets nil | The spawn failed, usually a command not on PATH. mantle log has the reason | process.run |
| A program runs twice after a save | A top-level process.detach launches again on every reload | session_process, What survives a reload |
Capabilities
A capability starts on the config’s first mantle.<name> read, so a
config that never reads it runs no server, watcher or agent. The quoted log lines need -v or -vv.
| Symptom | Cause | Fix |
|---|---|---|
A capability reads nil or stays inert | Its backend is missing or started after the shell (requirements); sysinfo and updates wait for configure; or the config reads mantle.<name> only inside a callback that never ran | Start the backend, then restart the shell: a reload does not retry. Call configure where the page says so. Read mantle.<name> at config top level |
battery.present is false | UPower’s display device is not a present battery (desktop, or battery not detected), or UPower is not running (state "unknown") | Expected on desktops; otherwise start upower.service; upower -d should list DisplayDevice |
| No tray icons | Config never reads mantle.tray; another host (waybar, snixembed) owns org.kde.StatusNotifierWatcher; the app is XEmbed-only; or it registered at an unlisted path before the shell started | Read mantle.tray; stop the other host; restart an SNI app so it registers again. An XEmbed-only app cannot show |
| Notifications not showing | Another daemon (mako, dunst, swaync) owns org.freedesktop.Notifications (another notification daemon already owns this name), or the config never reads mantle.notifications | Stop and disable the other daemon, then restart the shell. busctl --user status org.freedesktop.Notifications names the owner |
| No notification sound | DND or quiet on (critical still plays); the app is muted; the file is not Ogg Vorbis or 16-bit WAV, is over 4 MiB or 30 s, or lies outside the sound roots; sound-name with no tier sound registered | notifications backend |
| Polkit prompts not appearing | Config never reads mantle.polkit, so the agent never registers; another agent (polkit-gnome, hyprpolkitagent) registered first; $XDG_SESSION_ID unset | Read mantle.polkit; stop the other agent and restart the shell; start the session through logind |
| Polkit authentication always fails | /run/polkit/agent-helper.socket is missing, so the helper cannot run | Check that the installed polkit provides that socket |
| Idle never fires | A block-mode idle inhibitor is held (systemd-inhibit --list), a browser or player holds ScreenSaver, a Wayland surface inhibitor is up, or the config’s own inhibit is still held; or the compositor lacks ext_idle_notifier_v1 | mantle.idle.inhibited and inhibitors name the holder (empty who is the compositor) |
| Unlock refuses the right password | PAM stack login in use and pam_nologin or pam_shells refusing | Install the mantle PAM stack (install) |
| Locked session with no lock screen | The Renderer died and its replacement could not retake the lock (could not take the session lock over) | Switch VT and unlock through the compositor’s own mechanism |
| Keyboard layout switch does nothing | No supported compositor, only one layout configured (layout_count 1), or the compositor refused the switch | Configure several layouts in the compositor; on Hyprland, MANTLE_LOG=debug shows the reply to switchxkblayout main <i> |
Caps/Num Lock always false | No readable /dev/input keyboard with LEDs and no sysfs LED | Give the user read access to the input device |
brightness reads nil | No /sys/class/backlight device; external monitors are not covered | None; brightness is backlight-only |
| Brightness writes ignored | Session not active (another VT), so logind refuses SetBrightness | Switch back to the session |
| Pairing prompt never shows | The adapter is not visible and this shell did not start the pairing | Make the adapter visible or pair from the shell; PIN and passkey entry use a secure field |
See also: runtime, cli, signals, processes, capabilities, glossary (rescue, hydration, generation).
Source: reload and rescue, apply, check, callback logging, log subsystems.