Practice
Troubleshooting
Every way pyACAD and a bundle fail, listed by what you actually see, with the cause, the fix, and the log line that proves it.
pyACAD fails quietly by design. A bundle the parser cannot use is skipped so that one bad folder never takes the rest of your ribbon with it, and a boot stage that throws is logged and skipped so the stages after it still run. That is the right behaviour, and it is also why a broken tool can look like nothing happened at all.
The logs are the compensation. There is no debugger inside AutoCAD, so pyACAD writes a reason for every skip. This page is organised by symptom; each entry ends with the line to search for.
Where to look first#
| Where | What it tells you |
|---|---|
| The toast after boot or Reload | pyACAD toasts every problem it found: N bundle problems found, One command was not registered, pyACAD started with problems, Some dock panes could not be built. The first problem is quoted in full and the rest are in the log. |
%APPDATA%\pyACAD\logs\loader.log | The loader's own log, written before anything else exists. If pyACAD did not start at all, this is the only witness. |
%APPDATA%\pyACAD\logs\pyACAD.log | Everything the runtime does: the boot banner, every extension root, every skipped bundle, every registered command, every script failure with its traceback, every hook. |
pyacad env | Which AutoCAD years are installed and attached, and
which extension roots config.json lists. See
pyacad.exe and headless runs. |
PYACADCONSOLE | A message box with the IronPython version, the AutoCAD version and how many extensions loaded. A loader command, so it works when the ribbon does not. |
Both logs are plain text, appended forever, one line per event with a timestamp and
[INFO ] or [ERROR]. Search for ERROR first.
pyACAD does not load at all#
No pyACAD tab, and PYACAD is an unknown command. Work down this figure.
AutoCAD did not load the loader#
Cause. AutoCAD loads pyACAD as an Autodesk application bundle:
%APPDATA%\Autodesk\ApplicationPlugins\pyACAD.bundle, whose
PackageContents.xml has one block per AutoCAD year, each pointing at
Contents\<year>\PyAcad.dll and matched by release series
(R25.1 for 2026, R25.0 for 2025, down to R24.0 for 2021).
A year with no block, or a block for the wrong series, is never loaded.
Fix. Run pyacad env. A year that reads installed, not
attached has no loader: rerun the setup program with AutoCAD closed. The bundle
targets the AutoCAD* platform, so AutoCAD-based products of the same release
load it too.
The security prompt#
Cause. With SECURELOAD at its default of 1, AutoCAD stops at a
Security - Unsigned Executable File prompt before loading an unsigned DLL from a folder
it does not trust. If you dismissed it with Do Not Load, pyACAD did not load this
session. With SECURELOAD set to 2, AutoCAD loads executables only from trusted
folders and does not ask.
Fix. Choose Always Load at the prompt, or add the loader folder,
%APPDATA%\Autodesk\ApplicationPlugins\pyACAD.bundle\Contents\<year>, to
Trusted Locations on the Files tab of Options (the TRUSTEDPATHS
system variable), then restart AutoCAD.
The runtime could not be found#
pyACAD failed to start.
Could not load file or assembly 'PyAcad.Runtime' ...
Details: C:\Users\you\AppData\Roaming\pyACAD\logs\loader.logCause. The loader is a small DLL; everything else lives in the runtime
folder, %APPDATA%\pyACAD\<year>\runtime. The loader looks for it in this
order: the PYACAD_RUNTIME environment variable, a
"runtime<year>" key in config.json (such as
"runtime2026"), the per-user folder, then
%PROGRAMDATA%\pyACAD\<year>\runtime if only administrators can write to it.
When none exists, every runtime type fails to load.
Fix. Rerun the setup program. If you set PYACAD_RUNTIME or a
runtime<year> key, check it points at a folder that holds
PyAcad.Runtime.dll.
2026-10-15 08:59:12.004 [INFO ] AssemblyResolver installed. RuntimeDir = '<null>'
2026-10-15 08:59:12.031 [ERROR] config.json runtime dir does not exist: 'D:\old\runtime'The pyACAD tab is missing#
A boot stage failed#
pyACAD started with problems
Failed: Ribbon. Details are in C:\Users\you\AppData\Roaming\pyACAD\logsCause. Boot and Reload run their stages (event wiring, shortcuts, startup scripts, dock panes, ribbon markers, commands, ribbon, hooks, context rules, keyboard shortcuts) one by one, and a stage that throws is logged and skipped. The toast names every stage that failed.
Fix. Search the log for failed - continuing without it.; the
exception follows on the next lines. Then run PYACADRELOAD, which reruns every
stage and can heal a stage that failed at boot.
2026-10-15 09:14:03.120 [ERROR] 'Ribbon' failed - continuing without it.The tab vanished after a workspace switch#
Cause. Switching workspace, or loading a CUIx, rebuilds AutoCAD's ribbon from the customisation file, which knows nothing of pyACAD's tabs.
Fix. Usually none: pyACAD watches for it and puts its tabs back by itself,
logging Workspace loaded without the pyACAD tabs - rebuilding the ribbon. If the
tab does not come back, run PYACADRELOAD; the log will show why the automatic
rebuild did not happen (Workspace-load rebuild not hooked, or Ribbon rebuild
after workspace load failed).
The whole tab is missing, not just one button#
Cause. A panel with no items is not added to its tab, and a tab with no panels is not added to the ribbon. Emptiness propagates upward, so one broken button in a one-button extension removes the tab.
Fix. Fix the button, using the next section. There is no log line for the empty panel or tab, but there is one for whatever emptied them.
A button is missing#
After every boot and Reload, pyACAD counts the problems it found in the bundle folders and
toasts N bundle problems found, quoting the first one in full. If you saw it, the
answer is already on screen. If you did not, the cause is more likely a misspelled
.extension, .tab or .panel, or a root that is not
registered, which the scan cannot see.
You did not Reload#
Cause. The ribbon is built from a scan. New folders, renamed folders and new
bundle.yaml values are picked up by a rescan, not by the next click.
Fix. Click Reload on the pyACAD panel, or type
PYACADRELOAD. No AutoCAD restart is needed.
The extension root is not registered#
Cause. pyACAD scans the folders listed under "extensions" in
%APPDATA%\pyACAD\config.json, plus two defaults:
%APPDATA%\pyACAD\extensions, and %PROGRAMDATA%\pyACAD\extensions if
that folder exists and only administrators can write to it. An extension anywhere else is
never seen.
Fix. Register the folder that contains your
.extension folder, not the .extension folder itself:
pyacad extensions add D:\CadTools
pyacad extensions listOr add it in the Settings window, or edit config.json by hand, remembering
that JSON needs doubled backslashes:
{
"extensions": [
"D:\\CadTools"
]
}Log. Every root produces a line. A root missing from the log was never in
the config; a root reporting 0 extension(s) is registered but has no
*.extension folder directly inside it.
2026-10-15 09:14:02.418 [INFO ] Extension root 'D:\CadTools': 0 extension(s).The machine-wide root was skipped#
Cause. Anything in %PROGRAMDATA%\pyACAD\extensions runs for
every user on the PC, hooks and startup.py included, with no click. So that folder
is scanned only when ordinary users cannot write to it.
Fix. Restrict the folder to administrators (read-only for Users), or list
it under "extensions" in your own config.json to load it anyway.
[ERROR] Extension root 'C:\ProgramData\pyACAD\extensions' skipped: users other than administrators can write to it, so code in it could come from anyone on this PC. ...The .extension folder is nested too deep#
Cause. .extension folders are found directly under a
registered root. The scan does not recurse, so D:\CadTools\Team\Acme.extension
under a root of D:\CadTools is not found.
Fix. Move the extension up a level, or register
D:\CadTools\Team as a root of its own.
A folder suffix is misspelled#
Cause. Suffixes are matched case-insensitively but must be the exact word.
Count.pushButton works; Count.pushbuttons,
Count.push-button and Count.button do not.
Log. A folder inside a .panel whose name contains a dot is
taken to be an attempted bundle, so a bad bundle suffix is reported and counted into the
toast. A misspelled .extension, .tab or .panel is the
silent case: those are found by pattern, so a folder that does not match is never looked
at.
[ERROR] 'D:\CadTools\Acme.extension\Acme.tab\Blocks.panel\Count.pushbuttons' is not a bundle kind pyACAD knows (check the folder suffix) - skipped.The bundle is incomplete#
| Log line | Fix |
|---|---|
'...\Count.pushbutton' has no script.py - skipped. |
Add a file named exactly script.py. A bundle with only a
config.py is not a button. |
Stack '...' holds 4 button(s); a stack needs 2 or 3 - skipped. |
Two or three .pushbutton folders, counted after broken ones are dropped.
For more, use a .pulldown. |
Pulldown '...' has no usable pushbuttons - skipped. |
At least one .pushbutton child with a script.py. |
Urlbutton '...' has no url: in bundle.yaml - skipped. |
Add url:. |
Linkbutton '...' has no plugin: in bundle.yaml - skipped. |
Add plugin: with the command to send. |
Dockpane '...' has no pane.xaml - skipped. |
Add pane.xaml; the script.py is optional. |
Slideout '...' sits inside another slideout - skipped. |
One level of slideout only. |
It is there, but in the wrong place#
Cause. Order comes from the layout: list in the container's
own bundle.yaml (extension.yaml for tabs). A folder the list does not
name is not lost: it goes after every listed one, in name order, usually at the far end of the
panel. An entry that names no folder is reported and counted into the toast.
[ERROR] The layout in 'D:\CadTools\Acme.extension\Acme.tab\Blocks.panel' names 'Block Count', but no such folder is there - ignored.Fix. Spell the entry the way the folder is named. The suffix is optional
and case does not matter, but Block_Count is not Block Count.
The same extension is in two roots#
Cause. Two copies of one extension on two registered roots, typically a development copy and an installed copy. The copy in the root listed first builds the ribbon. The second copy's buttons still compete for command names, so you also get a commands were not registered toast for every one of them.
Fix. Unregister one root, or rename one extension (its folder, or
name: in extension.yaml).
[INFO ] Extension 'Acme' at 'C:\Users\you\AppData\Roaming\pyACAD\extensions\Acme.extension' is already loaded from an earlier root - skipped.The button is greyed out#
Cause. The bundle has a context: rule that is false right now:
selection with nothing selected, paperspace on the Model tab. An
unknown condition name is legal but always false, so a typo greys the button for good.
[ERROR] Bundle '...\Count.pushbutton': unknown context condition 'selected'.Fix. Use one of the eight conditions, doc,
selection, modelspace, paperspace, xrefs,
blocks, layouts and multi-doc. See
The bundle.yaml reference.
Commands#
A command was not registered#
One command was not registered
Command PYEXPORT is already taken, so 'Acme.tab/Data.panel/Export.pushbutton' has no command. Give it a different command: in bundle.yaml.Cause. Two bundles derive the same name (same title in two extensions), a
title derives one of the loader's names (PYACAD, PYACADRELOAD,
PYACADCONSOLE, PYACADRUN, PYACADSETTINGS), or AutoCAD
already has a command or a LISP C: function by that name (belongs to something
else in AutoCAD, so ... is ribbon-only). The first bundle in load order wins.
Fix. Give the loser a command: in its bundle.yaml
and Reload. The bundle still works from the ribbon meanwhile, run directly instead of as a
command. See AutoCAD commands and AutoLISP.
Typing the command says Unknown command#
Cause. The derived name follows the title. Retitle a bundle and its old command is gone after the next Reload; a new bundle has no command until a Reload.
Fix. Reload, then check the name in the log line Commands
registered: N (...), which lists every one. Pin names that anything else types with
command:.
Clicking does nothing#
Cause. A click sends _.PYNAME to the command line, like
typing. With no drawing open there is nowhere to send it, and you get an Open a drawing
first. toast.
Fix. Open a drawing, then click again.
(pyacad ...) returns nil#
Cause. Either the script set no __result__ (or one
json cannot serialise), or the run failed: the script raised, the target was not
found, another script was running, or the bundle is outside its
min_host_version/max_host_version.
[ERROR] (pyacad "PYLAYERCOUNT") failed: No script file or pyACAD command named 'PYLAYERCOUNT'.
[INFO ] Script global '__result__' was not captured: ...The script does not do what you wrote#
Nothing happens at all#
Cause. The work is in a function nobody calls. Defining
main() and never calling it loads cleanly, raises nothing and opens no window.
(It is not the __main__ guard: both engines set __name__ to
'__main__', so the guard is true.)
Fix. End the file with the call, or write module-level code. To be sure
the script runs at all, put toast.info('here') at the top.
A window opens with almost nothing in it#
Cause. Something called print(). The output window opens on the
first write, so a stray print, a debug line or a library warning is enough.
Fix. Use pyacad.toast for one-line statuses; keep the output
window for reports worth a window.
A toast says the tool requires another AutoCAD#
Requires AutoCAD 2025 or newer (this is 2024).Cause. min_host_version or max_host_version in the
bundle's bundle.yaml. The button still shows, by design, and refuses on
click.
A toast says something is still running#
Batch Plot is still running
Wait for it to finish, then try again.Cause. One pyACAD script runs at a time, process-wide. A long script pumps the message loop to repaint, and the pump delivers your second click, which is refused rather than run over the same drawing.
Fix. Wait. A script cannot be cancelled from outside; long tools must offer their own Cancel (see The script environment). A script that never ends needs an AutoCAD restart.
[INFO ] Refused 'Layer Mapper': 'Batch Plot' is still running.Errors#
eLockViolation#
Cause. Code wrote to the drawing without the document lock. A command has
it for free, but a hook, startup.py, a dock pane's event handler, the console or a
modeless window does not.
Fix. Wrap the write in with doc.lock():.
RuntimeError: No drawing is open.#
Cause. pyacad.doc.active(), and everything that reads the
drawing, needs an active drawing. A ribbon click never starts a script without one, but code
outside a command can run with none open: a hook after the last drawing closed, or
startup.py on a Reload with no drawing.
Fix. Check script.get_document_path() first; it returns
None when no drawing is open.
ImportError: could not load the AutoCAD .NET API#
ImportError: pyacad: could not load the AutoCAD .NET API (acdbmgd, acmgd, accoremgd).
This module only works inside an AutoCAD session running pyACAD.Cause. An API function of a pyacad module was called outside
AutoCAD: in a unit test, or a script run by a plain Python.
Fix. Inside AutoCAD nothing is wrong. Outside it, test the pure functions only, and keep your own logic in pure functions with no API import.
Unknown engine#
Unknown engine 'cpyton'.Cause. A typo in engine: in bundle.yaml or
extension.yaml. The only values are ironpython and
cpython, and the check happens at click time, so the button looks normal until it
is pressed.
No CPython found#
No CPython found. Install Python 3 (python.org) or set the "cpython" key in
%APPDATA%\pyACAD\config.json to a python3XX.dll or install dir.Cause. A bundle asks for engine: cpython and pyACAD found no
usable interpreter: none in config.json, none in PYACAD_CPYTHON, and
no 64-bit Python 3.7 to 3.13 in the registry. A 32-bit or ARM64 Python is ignored, and so is a
newer Python than 3.13.
Fix. Install a 64-bit Python in that range from python.org, or point the
"cpython" key at a python3XX.dll or its folder (the Settings window
has a field for it). Restart AutoCAD: the interpreter is chosen once per process.
import pyacad fails in every script#
Cause. The runtime could not find the library folder. Logged once, at boot.
[ERROR] pyacadlib not found (config 'pyacadlib' key or <runtime>\pyacadlib) - 'import pyacad' will fail.Fix. Clear a wrong "pyacadlib" key in
config.json (a missing folder there is also logged), or reinstall. Restart
AutoCAD.
import os fails on IronPython#
Cause. IronPython uses the standard library shipped in the runtime folder.
If that folder is missing, every standard-library import fails while pyacad
itself may still load. CPython bundles are unaffected.
[ERROR] Python stdlib not found at 'C:\Users\you\AppData\Roaming\pyACAD\2026\runtime\lib' - 'import os' etc. will fail on IronPython.Fix. Reinstall.
A traceback appeared in the output window#
Cause. Your script raised. The engine formats the traceback into the output window and the log on purpose, because there is no console to send it to.
Fix. Read the frames from the bottom up; the last one is your line. To fail
politely instead, catch the exception and report it with toast.error(message,
detail).
Hooks and startup.py#
A hook failed three times and went quiet#
Hook Acme:doc-saved disabled after 3 failures
Fix the script, then Reload pyACAD to re-enable it.Cause. A hook that fails three times in a row is disabled for the session, so a broken hook cannot fire a traceback on every selection change. A success in between resets the count.
Fix. Read the three tracebacks in the log (Hook Acme:doc-saved
failed:), fix the script, then Reload, which re-enables every hook.
A hook does not fire#
- The file name must be one of the 20 event names exactly, such as
doc-saved.py. Anything else is logged and skipped:Hook '...': unknown event 'on-save' - skipped. Valid names: ... - A new hook file needs a Reload.
- While a pyACAD script runs, hooks for other events are skipped (logged as
Hook ... skipped: '...' is running.), so a hook never shares the engine with a running command. - An event a hook causes does not start hooks again, so a hook cannot loop on itself.
- Under
pyacad runno hooks run at all.
See Hooks and startup scripts.
startup.py failed#
Acme startup.py failed
See the log; the extension's buttons still loaded.Cause and fix. The traceback is in the log after startup.py failed
for 'Acme'. The extension's buttons load regardless. Fix the script and Reload, which
runs every startup.py again.
A dock pane does not appear#
- Some dock panes could not be built, with the pane named: its
pane.xamldid not parse or itsscript.pyraised. The log line startsDock panes with problems:and the traceback follows. - No toggle on the ribbon: the bundle was skipped (no
pane.xaml), or the palette failed to build (Dock pane '...' has no palette - no button.). - It forgot its size and side: AutoCAD remembers each palette by an id derived from the bundle key, so renaming or moving the bundle folder starts it fresh.
A keyboard shortcut does not work#
A chord fires only on a fresh key press (not auto-repeat), only while the AutoCAD main window is in front, and never while focus is in a text box, the command line included, so it never steals a key you are typing. Click in the drawing area and try again.
Rejected bindings are logged with the reason:
[ERROR] Shortcut: 'Shift+M': bindings need Ctrl or Alt (set shortcuts.allowBareKeys to permit bare keys)
[ERROR] Shortcut: 'Ctrl+Esc': unknown token 'Esc'
[ERROR] Shortcut: 'Ctrl+A+B': more than one key
[ERROR] Shortcut: 'Acme.tab/Data.panel/Export.pushbutton' wants Ctrl+Shift+E but 'Acme.tab/Data.panel/Import.pushbutton' already has it - first wins
[ERROR] Shortcut: shortcut override for unknown tool 'Acme.tab/Data.panel/Export' ignoredThe last one is a config.json override keyed without the
.pushbutton suffix: keys are the full extension-relative bundle path. The
Shortcuts window writes them correctly for you. See Keyboard
shortcuts.
My edits do not take effect#
Three loops, at very different costs. Knowing which one an edit falls into saves more time than any other fact in this guide.
| What you edited | What it takes | Why |
|---|---|---|
script.py or config.py | Nothing, run it again | The file is read and compiled on every run. |
A module beside script.py, or in <extension>\lib |
Nothing, run it again | Dropped from the module cache before each run. |
| An existing hook's contents | Nothing, wait for the event | Hook files are read each time their event fires. |
bundle.yaml, extension.yaml, icons, folder names, new or
deleted bundles | Reload | Read when the extensions are scanned. |
| A new hook file, or one disabled by failures | Reload | The hook list is built by the scan, and Reload resets hook health. |
startup.py, pane.xaml, a dock pane's
script.py | Reload | They run, or are built, at boot and on every Reload. |
A module inside a *.lib folder, or the pyacad
library | Reload | Shared modules stay cached until Reload. |
config.json: extension roots, shortcuts, theme, ribbon hints |
Reload | Read again by every Reload. |
Adding or removing a whole *.lib folder | Restart AutoCAD | The engine search path is fixed when the engine starts; Reload toasts a reminder. |
config.json: cpython, pyacadlib,
runtime<year> | Restart AutoCAD | Read once, before or as the engines start. The Settings window says so. |
| The pyACAD runtime itself | Restart AutoCAD | The assembly is loaded into the process once. |
Headless runs#
pyacad run writes everything to its run folder,
%APPDATA%\pyACAD\runs\<yyyyMMdd-HHmmss>:
| You see | Look at |
|---|---|
No attached AutoCAD. Run pyacad attach <year> first, or give
--version. | pyacad env; the setup program attaches every
installed year. |
open-failed | console-N.txt: Core Console's own
output, including whether the drawing opened and whether NETLOAD of the
loader worked. |
failed | transcript-N.txt for the script's output and
traceback; response-N.json for the full error. |
timed-out | Usually a prompt the script did not guard with
__runner__: an Editor prompt waits forever in Core Console. |
save-failed | The error in
results.json: a read-only drawing, or one with no file. |
See pyacad.exe and headless runs.
Other tools for finding out#
Alt+Click#
Alt+Click any pyACAD button to open its bundle folder in Explorer with
script.py selected. The fastest way from "which file is this?" to the file.
The console#
The Console button (PYCONSOLE) opens an interactive IronPython
prompt inside AutoCAD with the pyACAD library on the path: try an expression against the open
drawing before you put it in a script. Ctrl+Enter runs; Up and Down at
the edges walk the history. It is not a command while you type in it, so wrap anything that
writes to the drawing in with doc.lock():.
from pyacad import doc, layers
doc.path()
len(layers.names())Logging from your own script#
from pyacad import script
log = script.get_logger()
log.info('Read %d rows from %s' % (len(rows), path))Lines go to pyACAD.log, labelled with your tool's title (or the hook or
startup.py they came from). Warnings and errors are also echoed into the output
window, but only when one is already open: logging never opens a window by itself.
debug lines are dropped unless you call log.set_level('debug').