Hermes plugin search misses the live catalog and stays silent

John Burns

hermes plugins search --json 0xarchive exited 0. Stderr was empty. The JSON object had query set to 0xarchive and results set to an empty array. The published catalog, generated at 2026-11-14T03:45:01.238695+00:00, contains that name at version 1.0.0, pinned to 65575eddfa9c33ec429626542455eecd423cf6a5. The decoded file that holds the name is 4,443,848 bytes. The loader in this binary refuses a live body larger than 2,097,152 bytes, logs the refusal at debug, and does not write a cache. Search then answers from the catalog shipped in the checkout.

An empty row is not a removal. It is the no-cache fallback. A name you copied from the website can be absent from search for that reason, while a name search does print can still be an older pin, or a name the published file no longer lists.

The length the loader compares

The binary was Hermes Agent v0.22.5+471.g5894e75, local commit 5894e750c3db, on Linux x86_64, on 13 November 2026. hermes --version also printed Python 3.14.9 for the agent runtime. HERMES_HOME pointed at a new directory. No login was run. No plugin was installed. The operator profile was not the home under test, and the gateway that was already running was not the process that answered search.

The catalog module in that commit sets LIVE_CATALOG_URL to https://hermes-agent.nousresearch.com/docs/api/plugin-catalog.json and _MAX_LIVE_BYTES to 2 * 1024 * 1024. The fetch follows redirects. On 14 November 2026 at 04:05 UTC the docs URL returned HTTP 301 to https://nousresearch.github.io/hermes-agent/docs/api/plugin-catalog.json. A curl that did not ask for compression saved 4,443,848 bytes. The saved response listed Content-Length: 4443848 and did not list Content-Encoding.

The loader does not use curl. It uses httpx. That client followed the same 301 and received Content-Encoding: gzip with Content-Length: 1396173. After decompression, resp.content was 4,443,848 bytes. The cap compares that decoded length. 1,396,173 is under 2,097,152. 4,443,848 is not. A check that stops at the compressed header will say the file fits. The process that decides whether search may use the file does not stop there.

The failure string, captured from the loader with debug logging on, was Plugin catalog: live fetch failed: live catalog payload too large. hermes plugins search did not print it. The new home’s logs/agent.log and logs/errors.log did not contain catalog or too large. The cache path cache/plugin-catalog.json was not created. The command’s success line, such as it is, is exit 0 and a JSON object. Nothing in that object says the live document was refused.

The module comment says a fetch failure reuses the last valid cached copy, then falls back to the in-tree catalog when no valid cache exists. This home had no cache, so the second sentence is the path that ran. A home that already has a cache file is a different path. After a failed fetch the loader can return that file while it is younger than 24 hours, and it will not even retry while the file is younger than 6 hours. This run did not replay an old cache. If cache/plugin-catalog.json exists, read its mtime before you treat an empty search as the checkout fallback measured here.

What an empty result is standing in for

The published document held 2,309 entries. load_catalog() on this checkout returned 800. Of the published names, 1,516 were absent from the checkout. Search for one of them, 0xarchive, returned no row. Search for 0bull, which the checkout does contain, returned one row, version 1.0.0, sha 240890a71715375bc877b238491e414517e9d902. Search is working. It is not reading the file the website just published.

The v0.23.1 release notes, dated 9 November 2026, say the published catalog had already passed the 2 MiB cap that older clients accept, so install fell back to the catalog the release shipped with. A client on that path could not find plugins added since the last update, could install the pin the release shipped instead of the current one, and could miss newer removed entries. The note says v0.23.1 raises the cap to 16 MiB. The tagged source at v0.23.1 sets _MAX_LIVE_BYTES to 16 * 1024 * 1024 and comments that the published document passed 2 MiB on 9 November 2026 at 2,109,306 bytes for 1,268 entries. This run did not install v0.23.1. Nothing below is a measurement of that binary. The file measured here is larger than the 9 November figure: 4,443,848 bytes and 2,309 entries. That size is still under 16 MiB. It is over the constant in 0.22.5. Staying on this binary keeps the refusal the notes describe.

The kill-list half of that note did not show up as a new name today. The published removed array and the checkout’s removed.yaml each held one entry, or-picker, dated 2026-09-28, with the reason that the provider was folded into openrouter-picker. There was no removal present only in the live document. Do not read this run as proof that a future removal will be enforced. The code unions the in-tree kill list with the live kill list, and the live list is empty when the fetch returns no document and no cache. A removal published after this checkout, and absent from removed.yaml, would not be in the list this process built. That case was not in the file on 14 November. The miss that was in the file is the 1,516 names and the stale pins.

A pin search will still offer

Sharing a name is not the same as sharing a pin. 793 names were in both catalogs. 216 of those names had a different sha. adaption is one of them.

Search returned:

name: adaption
version: 0.1.0
sha: 958ad2e9077107be1919a5365541caa4e0acdcc8
requires_hermes: >=0.21.5

The published entry for the same name was version 0.3.0, sha 3ab5264a043ea65ad03f2fbad841219d9649fb6a. The checkout pin is what an install from this process will clone if it resolves the catalog name. The website pin is not consulted after the size refusal. The release note’s sentence about installing the pin your release shipped, rather than the current one, matches this row. This run did not run hermes plugins install, so it does not show the clone, the consent prompt, or the directory that would have been written. It shows the pin search will hand to that install.

Other drifted rows in the same comparison included advanced-delegation (checkout 1.0.0, published 1.1.1), agent-hint (0.5.1 to 0.7.1), agent-importers (1.1.0 to 1.2.1), agent-station (3.0.3 to 3.1.0), and agentplaybooks-memory (0.2.0 to 0.4.0). Those six are samples of the 216, not a claim that every drift is a version-label bump. The sha is the pin. The version string is a label the catalog stores beside it. Compare the sha if you are deciding whether the checkout is current.

577 shared names had the same sha. A match on one name does not tell you the catalogs agree. 0bull matched and adaption did not, in the same search process, against the same refused document.

Names the published file has dropped

The miss also runs the other way. Seven names in the checkout catalog were absent from the published entries: agent-janitor, agent-ledger, context-audit, hermes-subscription-meter, machine-library, skill-integrity, and web-oxylabs. Search for agent-janitor returned one row, version 0.3.1, sha f8306948aa8edd0f7197dc9890ccd4fa19dd7aee. The published document does not list that name, and it is not in the removed array.

Search will still offer it. An operator who treats search as the current curated list will see a plugin the published catalog has dropped, with no removal reason attached, because the live removal list was never loaded. Whether that drop was a policy removal, a rename, or a catalog rebuild that omitted the file is not something this run can settle. The published entries and the published removed array are the two lists the website is serving. agent-janitor is in neither. The checkout still has a yaml entry, and search prints it.

That is a reason not to install from search alone while the live fetch is failing. The checkout copy is the fallback, not a second opinion that has already applied the website’s current kill list.

Verify the catalog before you install

Do this before you treat a website name as installable, and before you treat an empty search as a removal.

Point HERMES_HOME at a new directory when you want the no-cache path this run measured. An existing home may still be serving cache/plugin-catalog.json. Record the binary first.

hermes --version

Save the published document and its size. The decoded length is the number to compare with 2,097,152 on this release. If you also have a client that asks for gzip, record that compressed Content-Length separately and do not use it as the cap check.

curl -fsSL -D headers.txt -o plugin-catalog.json \
  https://hermes-agent.nousresearch.com/docs/api/plugin-catalog.json
wc -c plugin-catalog.json

On this run wc printed 4443848. The headers after the 301 listed the github.io URL and the same length. Confirm the JSON generated_at so you know which publish you measured. Then search for a name that exists only in that file, and for a name you know the checkout has.

hermes plugins search --json 0xarchive
hermes plugins search --json 0bull

Empty results and exit 0, with empty stderr, is the refusal measured here when the home has no cache. A row for 0bull is the sign that search itself ran. If both names are empty, you are not looking at this fallback. You are looking at a search that did not load the checkout catalog either, and the size check is not the explanation until that is sorted out.

Then compare one shared name to the published sha.

hermes plugins search --json adaption

If the sha in the JSON is 958ad2e9077107be1919a5365541caa4e0acdcc8 and the published entry is 3ab5264a043ea65ad03f2fbad841219d9649fb6a, this process is still on the checkout pin. Installing adaption from search will not fetch the published pin. Updating Hermes is the change that moves the cap. Re-running search on 0.22.5 will refuse the same oversized body again.

After search, confirm the new home has no cache/plugin-catalog.json. A file there means a fetch was accepted, or a cache was copied in. This run’s search left the cache directory without that file. It did create the usual empty profile skeleton. That skeleton is not a catalog.

Do not install a live-only name by cloning the repo URL from the JSON as a way around the cap. The cap is what kept this process from treating the published document as the curated list. Bypassing it means you are installing a commit the running client refused to accept as a catalog entry. Review that commit on its own terms, or move to a binary whose cap is large enough to load the document and then use search again.

What the next release changes, and what it does not prove here

v0.23.1’s source raises the cap from 2 MiB to 16 MiB and says the live catalog is used again. 4,443,848 is under 16,777,216, so the constant in that tag would not refuse the file measured here. This host did not run that tag. A later publish can pass 16 MiB, and a client that has a cache from before the bump can keep serving the old document until the cache rules say otherwise. After an upgrade, check the decoded size, delete a stale cache/plugin-catalog.json if you need the next search to refetch, and require a row for a name that was missing on 0.22.5 before you treat the live catalog as loaded.

The useful boundary on this binary is smaller than that upgrade. hermes plugins search exits 0 when the published catalog is too large to accept. The names it prints are the checkout’s names and the checkout’s pins. The names it omits are not a kill list. Read the decoded length, then read the sha, before you install.

Sources

  • Hermes Agent v0.22.5+471.g5894e75, commit 5894e750c3db, on Linux x86_64, 13 November 2026. hermes plugins search --json against a new HERMES_HOME was the user-facing measurement. The loader’s debug line was captured from fetch_live_catalog in that same checkout, with debug logging enabled. No plugin install was run.
  • hermes_cli/plugin_catalog.py at that commit: LIVE_CATALOG_URL, _MAX_LIVE_BYTES = 2 * 1024 * 1024, and the fallback when fetch_live_catalog returns no document.
  • Published catalog https://hermes-agent.nousresearch.com/docs/api/plugin-catalog.json, fetched 14 November 2026, generated_at 2026-11-14T03:45:01.238695+00:00, decoded length 4,443,848 bytes, 2,309 entries. The 301 target was https://nousresearch.github.io/hermes-agent/docs/api/plugin-catalog.json.
  • Hermes Agent v0.23.1 release notes, 9 November 2026. The note describes the silent fallback and the cap change to 16 MiB. This run did not execute that release.
  • hermes_cli/plugin_catalog.py at tag v0.23.1, which sets _MAX_LIVE_BYTES to 16 * 1024 * 1024 and records the 9 November size of 2,109,306 bytes. Source reading only.