skip to content

A ZAP add-on archive sits in the plugin directory but its capability is missing. Why?

level: seniorimportance: should knowfreq 42%

answer

  1. installed is not the same as running
  2. two gates, and they fail differently
  3. one gate hides it, one gate lists it
  4. extensions have their own requirements
  5. the warning only fires on a regression

basics

~20 s

Installed is not running. ZAP drops an add-on before loading if the block list or its declared program-version range rejects it, and skips it afterwards if its run requirements fail — a dependency issue, a newer minimum Java version, or missing bundled libraries.

solid answer

~50 s

Loading runs two gates. First `canLoadAddOn` removes the add-on from the collection entirely if it is on the block list or if its declared program-version range excludes the running version; that removal is logged at debug only. Then `calculateRunRequirements` builds an `AddOnRunRequirements`, and a failure there — a `DependencyIssue` of `CYCLIC`, `MISSING`, `OLDER_VERSION` or `VERSION`, a newer minimum Java version, or missing bundled libraries — leaves the add-on **in the collection but not loaded**. So the add-on listing still shows it, at its version and status, while none of its code runs. There is a partial case too: an add-on can be runnable while individual extensions inside it are not, so only part of its surface appears. Headless ZAP warns about this only for add-ons that ran on a previous start against the same home directory.

code

bash · 9 lines
bash
# Is the add-on in the collection at all? A blocked or version-barred one is not listed.
zap.sh -cmd -addonlist | grep -i openapi

# Listed but inert means the run requirements failed. That warning reaches stdout,
# but only for an add-on that DID run on a previous start in this home directory.
zap.sh -cmd -addonlist 2>&1 | grep -i 'no longer be run'

# And the command-line symptom of an add-on that is not running at all:
zap.sh -cmd -autorun plan.yaml   # -> Unsupported option '-autorun'.

go deeper

for a junior

Learn the distinction before the mechanism: an add-on file being present does not mean its features are available. Checking the add-on listing is the first thing to do, and it is not the last.

for a middle

Explain the run requirements — dependency issues, a newer minimum Java version, missing bundled libraries — and be able to say why a failure there still leaves the add-on in the listing.

for a senior

Demonstrate the diagnosis under time pressure: list, read the startup output, try the argument the add-on registers, then check the dependency rather than the add-on. Know that a fresh home directory suppresses the warning you were hoping for.

for a principal

Treat silent capability loss as a design problem, not a support problem. If a pipeline can lose passive scanning or an importer without turning red, the gate is measuring the wrong thing, and that is a decision about verification design rather than about ZAP.

## Two gates, and they fail differently When ZAP starts it walks the archives it found and applies two separate checks, in order. **Gate one — `canLoadAddOn`.** An add-on is dropped here if its id is on the **block list** (the record kept when a user uninstalled an add-on whose file could not be deleted), or if its declared program-version range excludes the running version. This gate **removes the add-on from the collection**, and it logs the reason at debug level. Nothing at normal log level mentions it. **Gate two — `calculateRunRequirements`.** For everything that survived, ZAP builds an `AddOnRunRequirements` and asks whether it is runnable. Failures come from three places: - a **`DependencyIssue`**: `CYCLIC` (a dependency cycle), `MISSING` (a required add-on is not installed), `OLDER_VERSION` (an older copy of the add-on is still present) or `VERSION` (the dependency is installed at a version outside the declared range); - a **minimum Java version** that the running runtime does not meet — and the requirement may come from a dependency rather than from the add-on itself; - **missing bundled libraries**, where the archive's declared libs are not on disk. An add-on that fails gate two is **not removed from the collection**. It is simply never given a class loader and never installed. That is the asymmetry that makes this hard to see. | symptom | gate one | gate two | |---|---|---| | appears in the add-on listing | no | **yes** | | any of its code runs | no | no | | logged at normal level | no | only as a delta (below) | ## The partial case: half an add-on Run requirements are computed for the add-on **and separately for each of its extensions that declares dependencies**. `getExtensionRequirements()` returns those, and `hasExtensionsWithRunningIssues()` reports whether any failed. An add-on whose own requirements are satisfied still loads, and the extensions that failed are simply left out of its runnable list. So an add-on can be present, listed, loaded, and still be missing the one extension you actually needed. ## Why the log so often says nothing Headless startup calls a routine that warns *"Add-on … or its extensions will no longer be run until its requirements are restored"*. It is the right message, and it fires for the wrong set: it iterates only the ids that were **runnable on a previous start and are not now**, computed against a persisted run-state file. Two consequences: 1. A **fresh container** has no previous state, so an add-on whose requirements never held produces **no warning at all** — it is not a regression, it is simply absent. 2. The same silence follows any run in a clean home directory, which is the normal shape of a CI job. This is the specific reason "the add-on is installed and nothing happened" is a recurring support question rather than an obvious one. ## Diagnosing it 1. **List what the install thinks it has.** The add-on listing prints name, id, version, status and description. An add-on rejected at gate one is absent from it; one that failed gate two is present. That asymmetry is the single most informative reading. 2. **Read the startup output, not just the scan output.** The run-requirement warning goes to the log and to standard output, and it names the add-on and the issue. 3. **Try the argument the add-on registers.** Add-ons register their command-line arguments through the extension hook, and the unsupported-argument check runs only on the final parse pass, after every loaded add-on has had its chance to claim one. So an argument that comes back as an unsupported option is a strong signal that nothing registered it. 4. **Check the dependency, not just the add-on.** `MISSING` and `VERSION` issues point at a *different* add-on. Installing the one you wanted without its dependency is the most common way to land here. ## The trap in step three The unsupported-argument check is skipped entirely when the same command line also carries an install argument — because the add-on being installed on this run is the one that would register the flag, so it cannot be known yet. The effect is that a typo on a line that also installs an add-on is **silently accepted**, while the same typo alone fails loudly. If you are scripting an install-then-run invocation, split it. ## The sentence to keep *Present, loaded and running are three different states.* The add-on listing answers the first, the startup log answers the second only when the capability used to work, and the third is best confirmed by exercising the add-on's own surface.

  • Why does the add-on listing show an add-on whose capability is not running?
    Because the two gates behave differently. An add-on rejected for the block list or a program-version constraint is removed from the collection, so the listing cannot show it. An add-on that failed its run requirements stays in the collection and is only denied a class loader, so it is still listed at its version and status. The listing therefore answers "is it here", never "is it running".
  • Can an add-on load and still be missing part of its surface?
    Yes. Extensions that declare dependencies get their own run requirements, and an add-on whose own requirements are met loads with the failing extensions simply left out of its runnable list. So one job type, one API component or one script type can be absent while everything else from the same add-on works.
  • Why can a typo'd argument be accepted silently?
    The unsupported-argument check is suppressed when the same command line also carries an add-on install argument, because the add-on being installed would be the thing that registers an unknown flag. So `-addoninstall foo -typo` is accepted where `-typo` alone would fail. Split installation from the run so the check stays live.

saying these in an interview costs you the question

  • Assumes an add-on in the plugin directory is necessarily running.
  • Reads the add-on listing as proof the capability is live.
  • Expects a warning in the log whenever an add-on fails to load.
  • Forgets that a dependency add-on can be the thing that is missing.
  • Thinks a partly loaded add-on is impossible — it is all or nothing.