cd /news/ai-tools/claude-code-mod-not-loading-or-rende… · home › topics › ai-tools › article
[ARTICLE · art-148299] src=dev.to ↗ pub= topic=ai-tools verified=true sentiment=· neutral

Claude Code mod not loading or rendering with no error? Where to look

A developer documented the three failure modes that cause Claude Code mods to silently fail without errors: static analysis rejection that prevents the whole plugin from loading, hooks skipped for using unavailable runtime APIs like process or setTimeout, and component rule violations that make Claude Code render its default UI. The writeup recommends running `claude plugin validate` and searching the debug log for the mod's name, and notes that the desktop app bundles an older Claude Code (2.1.286) than the terminal, so events such as ui.fault require v2.1.289 or later.

by read8 min views1 publishedOct 9, 2026

When a mod does nothing and shows no error, the cause almost always falls into one of three groups: (1) it failed static analysis and the whole mod was not loaded, (2) it used something the hook runtime does not have and the hook was skipped, or (3) the tree it returned broke the component rules, so Claude Code drew its own UI instead. Start with claude plugin validate, then search the debug log for the mod's name.

The official troubleshooting page opens with this: when a mod fails to load or a hook fails, Claude Code skips it and keeps the session going. A broken mod looks the same as a mod that does nothing.

That design keeps Claude Code itself from going down, but it leaves the author with few clues. So decide in advance where to look.

Symptom Where to look Common causes
No command, no band, nothing 1. Static analysis Put $ in a variable, passed$ to a function in another file, dynamic import, an event not in this version
Loaded, but some hooks do not run 2. Runtime Used process orsetTimeout , ran over 10 seconds
The band or panel shows the default UI 3. Component rules Passed undefined orfalse , missing a required field, a component not available on that surface

claude plugin validate

claude plugin validate .\my-mod

Without starting a session, this runs the same static analysis Claude Code runs at load time. It catches misspelled events, broken manifests, and unreadable modules.

hooks: line appears: hooks/hooks.json has no modules, or it is misspelled At load time, the module source is analyzed, and if it fails, the whole plugin is not loaded.

Code Fix
const s = $.store; s.get(k) Write $.store.get(k) in full
const { store } = $ Do not destructure
helper($) (wherehelper is in another file) You can pass $ only to top-level functions in the same file
Passing $ to a function created inside a hook Move the function to the top level
on(eventName, ...) (name in a variable) Write the event name as a string literal
Declaring another variable named on insideregister Rename it
await import('./x.mjs') Dynamic import is not allowed. Use a top-level import statement
Importing a file outside the plugin Copy it into the plugin. The only outside import allowed is claude-code
require(...) Use ES module import

Three things are easy to miss:

on() calls in files you import.AbovePrompt, register once and dispatch inside That last one hits the desktop app in particular. The desktop app bundles its own Claude Code, which can be older than your terminal version. I had a mod that registered ui.fault (the event for Client component failures). It worked in the terminal but vanished entirely on the desktop (bundled 2.1.286). The official reference now says ui.fault requires v2.1.289 or later. When you use a new event, compare the version note in the reference with the version shown by /status on the desktop.

Hooks run in an environment that is neither Node nor a browser. The official API page also says there are no Node.js APIs and no timers like setTimeout.

process, setTimeout, setInterval, Buffer, the DOM, require URL, TextEncoder/ TextDecoder, AbortController, crypto (including crypto.subtle), atob/ btoa, structuredClone, performance

What you want Use instead
Wait $.clock.sleep(ms)
Run once later $.clock.after(ms, fn)
Run periodically $.clock.every(ms, fn) (create it insession.start )
Current time await $.clock.now()
Files, network, processes $.fs ,$.http ,$.process

In your own code, you just rewrite these. The trouble comes when you copy in an npm package: it reads process.env the moment it loads, throws, and the whole mod silently disappears. Putting local stand-ins at the top of that package's file, scoped to that file only, got it through.

const process = { env: {} }
const setTimeout = () => 0

The other issue is time. These are the limits from the official reference that people hit most often.

Thing Limit
A hook's own run time per call (waiting inside next and APIs does not count;$.clock.sleep does) 10 seconds
A .catch handler 1 second
All session.end hooks 1.5 seconds combined (configurable)
$.process.run 30 seconds by default, 10 minutes max

Hooks that run longer than 10 seconds are skipped. If you do heavy work in a guard that blocks tool calls (tool.check), the guard itself gets bypassed, so be careful.

If the tree returned from ui.render breaks the rules, Claude Code draws its own UI without any error. Your band just disappears and the default comes back, which makes this the hardest one to notice.

Code Result
Passing a field whose value is undefined May be treated as invalid
autoFocus: false ,focus: false Throws. Pass only true , or omit it
plain: false on aButton Cannot be drawn on the desktop. Use true or omit it
Svg withoutsource oralt Not drawn (both are required)
undefined inClient props Invalid (JSON values only)
A component not available on that surface ( Raster on the desktop, etc.) Invalid
Props a component does not accept (including typos) Invalid

Conditional fields naturally let undefined slip in, so I wrap the component factories in one place and strip those values.

// Do not pass fields that have no value to components
function strip(raw) {
  const out = {}
  for (const k of Object.keys(raw)) {
    const make = raw[k]
    out[k] = typeof make === 'function'
      ? (props = {}) => make(Object.fromEntries(Object.entries(props).filter(([, v]) => v !== undefined)))
      : make
  }
  return out
}

const t = strip($.ui.resolve(e))
t.Button({ key: 'pdf', label: 'PDF', plain: true, dimColor: busy ? true : undefined, onPress })

Writing dimColor: busy ? true : undefined avoids ever passing false.

When a module fails to load, a hook is skipped, or a tree is rejected, Claude Code writes one line containing the mod's name. Where it goes depends on the session (official troubleshooting page).

Session Where the line appears
Interactive session loaded with --plugin-dir A faint line in the conversation
Normal session with a mod installed from a marketplace Debug log only
claude -p with--plugin-dir Standard error

In other words, when an installed mod is silent, no amount of looking at the conversation will show anything. Capture a debug log.

claude --debug-file .\mod-debug.log --plugin-dir .\my-mod

Watch the log in another window (PowerShell example).

Get-Content .\mod-debug.log -Wait | Select-String my-mod

These are the strings to look for:

Log text Meaning
hooks module my-mod@inline loaded ... events: ... Loaded, with the list of registered events
hooks module my-mod ... not loaded: Rejected by settings or policy. The reason follows the colon
hooks module did not load: Top-level code threw. File and line are shown
hook skipped: A hook threw, timed out, or returned the wrong shape
ui.render (Pane) refused: /a hook returned a tree that does not validate The tree broke the component rules. Reason included
it crashed the hooks worker The mod was removed, for example because of a loop that never yields

A line like ui.render (Pane) refused: Text prop "bogusProp" is not allowed; the engine drew its own tells you exactly which prop on which component is wrong. Problems in group 3, component rules, are almost always solved in one look at this line.

To write your own markers, use $.ui.log('got here', { to: 'debug' }) to write to the log.

The desktop app cannot take launch flags, so the approach above does not work as is. How to capture a debug log from the desktop app is not described on the official pages, so it is not verified. My routine is to load the same mod in the terminal with --debug-file, find the reason line, fix it, and then check in the desktop app. However, things that fail only on the desktop, like an event missing from that version, do not reproduce in the terminal, so check the desktop version with /status first.

Before blaming your mod, mods may be turned off by configuration. Run claude plugin test in a folder with no mod to see the state.

Output Meaning
no hooks module to load Mods can load (there is just nothing to test in this folder)
hooks modules are turned off here Turned off by disableAllHooks or an organization policy
hooks modules are turned off in this process Turned off remotely by Anthropic

Other causes of "nothing loads at all" include not having answered the trust prompt for a newly opened folder, and starting with --safe-mode or --bare.

undefined, false, required fields, and components not available on that surface To see how other mods are written so they pass static analysis, read them in the modscode reviewed list. Every mod listed there is code that claude plugin validate can read.

This article was written with AI assistance (Claude) and checked against the official Claude Code docs; anything marked "not verified" could not be confirmed there.

── more in #ai-tools 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/claude-code-mod-not-…] indexed:0 read:8min 2026-10-09 · —