Skip to content

Troubleshoot a mod

Work out why a Claude Code mod does nothing - where Claude Code reports problems, every refusal and skip message, drawing failures, lost state and the debug log.

When a mod's module or one of its hooks fails, Claude Code skips it and carries on. That keeps your session alive, but it means a broken mod usually looks like a mod that simply does nothing. This page is the checklist I work through, organised by symptom.

First two moves

1. See what Claude Code reads from the files, without starting a session:

claude plugin validate ./edit-meter

This catches misspelled events, bad manifests and unreadable modules. Check the hooks: and calls: lines are what you expect (see /docs/plugins/mods/create).

2. Find the line Claude Code wrote. When a module fails to load, a hook is skipped, or another mod refuses yours, Claude Code writes one line naming your mod. Where it goes depends on the session:

SessionWhere the line appears
Interactive with --plugin-dir, or with hot reloading enabled for Claude-written modsA dim line in the transcript
Any other interactive session (for example a marketplace-installed mod)The debug log only; start with claude --debug
claude -p with --plugin-dirstderr, in the default text output. Refusals by other mods go to the debug log only

Can mods load here at all?

Run claude plugin test from a folder that contains no mod. No session needed:

Output includesMeaning
no hooks module to loadMods can load; there was just nothing to test
hooks modules are turned off hereA setting blocks mods: your own disableAllHooks, or organisation policy
hooks modules are turned off in this processAnthropic has switched installed mods off remotely; nothing local re-enables them

An organisation's allowManagedModsOnly is not reported here. If it applies, installing a mod produces a guard message instead.

Nothing from the mod appears

No command, no drawing, no behaviour change.

CheckCause and fix
VersionMods need v2.1.287+ in the terminal, v2.1.286+ inside the Desktop app. See /docs/plugins/mods/overview
/plugin shows no mods active line naming itThe module did not load. Look in the debug log for hooks module <name> not loaded: and read the refusal reason
claude -p prints hooks module not loaded on stderrSame: a refusal. Read the reason after the colon
validate passes but prints no hooks linehooks/hooks.json has no modules key, or it is misspelled. Add "modules": ["./register.js"]
<name>: hooks module did not load: <reason>Your module could not load, for example top-level code threw. The reason includes file and line
hooks module did not load: options do not fit plugin.json userConfig:A userConfig value fails validation (say, above max) or a required one is missing. The line names the pluginConfigs entry in settings.json to fix
First time in this folderThe workspace trust prompt was never accepted. Run claude there interactively and accept it
No installed plugin loads at allYou started with --safe-mode

Remember that some settings stop mod code but leave the rest of the plugin (skills, commands, MCP servers) working, so a half-working plugin is a clue. /docs/plugins/mods/overview lists them.

Refusal reasons

These follow hooks module <name> not loaded: in the debug log. A --plugin-dir mod appears as <name>@inline.

Reason starts withMeaning
hooks modules are turned off for installed plugins in this processRemote switch-off by Anthropic; nothing local helps
disableAllHooks in managed settingsYour organisation turned off hooks from installed plugins
only managed plugins and built-in plugins runallowManagedHooksOnly is set, or disableAllHooks is set somewhere other than managed settings
installed plugins that are not managed load no hooks module in this mode (--bare)You started with --bare
another plugin of that name loads firstName clash. The managed one, or whichever loaded first, wins. Rename yours

Messages from the built-in guard

On machines with managed settings, or for users signed in on Team or Enterprise, the built-in guard can refuse a mod or one of its answers. Each message names the admin option that controls the rule.

Message containsMeaningAppears in
mods are limited to your organization's by policy (allowManagedModsOnly)Only organisation mods may run; yours was refusedDebug log, and the transcript in hot-reload sessions
tried to lift a deny rule in your settingsYour tool.check hook approved a call a deny rule refuses. It stays deniedTranscript and debug log, once per mod per session (debug log only under -p)
the deny rules in your settings could not be checked for this call, so it is refusedThe guard failed while checking a mod-approved call, so it refusedThe reason Claude reads for the denied call

See /docs/plugins/mods/admin for the admin side.

A hook is skipped or the mod is unloaded

The mod loaded, but something later went wrong.

MessageCauseFix
<mod>: <event> hook skipped: <reason>, e.g. edit-meter: tool.call hook skipped: threw Error: boomThe hook threw, exceeded its time limit, or returned the wrong shape. Shown once per event and failure kind until reload; the debug log has every occurrenceFix the hook. Add a .catch for guards (see /docs/plugins/mods/events)
<mod> registered /<cmd> but no command.run hook answered itThe command reached the end of the chain unanswered: no command.run hook, a matcher naming a different command, a hook that returned next(e), or a hook that was skipped (passing focus: false to $.ui.open is one way)Look for a hook skipped line naming command.run. A test running the command fails with the same reason
<mod> was unloaded: it crashed the hooks workerInstalled mods share one worker thread, and this mod was traced as the cause of a hang or crash, for example a loop that never awaitsFix the blocking code
hooks: mods that run in the hooks worker are off for this session: it crashed 3 timesThe worker stopped three times without a single culprit, so every non-built-in mod (organisation mods included) was unloaded. Shown in every interactive sessionRun /reload-plugins

A tool call is unexpectedly denied

MessageCauseFix
a hook changed this call's input after the model wrote it (auto mode)A mod's tool.call or turn.step hook, or a PreToolUse settings hook, changed the input after the server-side classifier reviewed it, so the review no longer covers it. The message does not say whichClaude is told to retry as recorded. If it is denied again, the hook rewrites every time: disable the mod or hook, or leave auto mode and approve manually. See /docs/permission-modes
Messages about deny rules in your settingsThe built-in guardSee the guard table

A drawing is missing or unresponsive

SymptomCauseFix
Pane or band empty, or shows Claude Code's normal contentYour tree failed validation. With --plugin-dir you get ui.render (Pane) refused: <reason>, e.g. Box prop "flexDirection" must be one of row, column, row-reverse, column-reverse; the engine drew its own. The debug log says a hook returned a tree that does not validateFix the named prop or element. Usual suspects: an unsupported prop, or an element the app lacks
$.ui.open runs but no pane appearsOpened without user action in a terminal narrower than 144 columns (110 once the user has opened it)Open from a command or button, and check isPlaced on the result. See /docs/plugins/mods/interface
Hotkeys do nothingPane lacks keyboard focusCtrl+X then Tab, click it, or open with focus: true from a command
Works in terminal, not DesktopSite or element not available there (for example Raster)Check the render sites and elements tables; branch on e.surface

Edits or values go missing

SymptomCauseFix
Your code changes have no effectYou are editing source but running an installed copy, which is cached by versionDevelop with claude --plugin-dir ./your-mod, which hot-reloads on save
A value resets on every saveModule-level variables reinitialise on reloadKeep it in $.state or $.store (/docs/plugins/mods/interface)
A value resets after /clear, /resume or /branch, or a stored value is overwritten with a defaultThose commands reset $.state, and session.start does not fire againReload from the store in a classic.SessionStart hook (how)

The debug log

The debug log records every module loaded or refused, every hook failure and every refused result, so it is the place to look when the transcript is silent.

claude --debug-file ./mods.log --plugin-dir ./edit-meter

In a second terminal:

tail -f ./mods.log | grep edit-meter

A healthy load looks like this (note @inline for --plugin-dir mods):

hooks module edit-meter@inline loaded (worker, environment 2, tier user); events: session.start,tool.call,command.run,ui.render

Invalid drawings are logged as refused results too. To write your own lines into the log, pass a second argument to $.ui.log:

$.ui.log('pins loaded: ' + pins.length, { to: 'debug' })

Without it, $.ui.log writes a dim transcript line instead.

While editing a --plugin-dir mod, each reload adds a transcript line naming the mod and its hooks. If a save breaks the module you see reload failed, the previous version stays loaded: plus the reason, and the last good version keeps running, which is why "my change did nothing" is often really "my change failed to load".

Problems with installing or loading the plugin itself, rather than its mod code, are covered in /docs/plugins/troubleshooting.