Skip to content

fix(plugins): warn when required env vars are missing, before register() - #68703

Open
ygd58 wants to merge 2 commits into
NousResearch:mainfrom
ygd58:fix/plugins-warn-missing-env-v3
Open

fix(plugins): warn when required env vars are missing, before register()#68703
ygd58 wants to merge 2 commits into
NousResearch:mainfrom
ygd58:fix/plugins-warn-missing-env-v3

Conversation

@ygd58

@ygd58 ygd58 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Context

Ports #63050 forward onto current main per @teknium1's review.

Problem

_load_plugin() emitted only a DEBUG registration count, so a plugin whose required env vars were absent loaded silently (issue #2768).

Fix

Normalize requires_env entries (str or dict) before checking the process environment; emit a WARNING naming each missing variable.

Per review, fixes two real bugs in the original port:

  1. The check ran AFTER register_fn(ctx). A plugin that reads the missing variable directly during registration raises straight into the generic 'Failed to load plugin' handler before the specific diagnostic is ever reported. Moved the check before register_fn(ctx).
  2. The provider-only regression test used a no-op register(), which didn't actually exercise a provider registration. Replaced with a real ImageGenProvider subclass registered via ctx.register_image_gen_provider().

Scope note: this fix is in the general PluginManager loader. The Hindsight memory plugin path uses a separate loader (plugins/memory/init.py) that general discovery skips entirely -- this PR does not touch that path.

Verification

Added a raising-register() regression test and the corrected real-provider test. 5/5 new tests pass; 121/121 in the full tests/hermes_cli/test_plugins.py file.

Ports NousResearch#63050 forward onto current main per teknium1's review.

_load_plugin() emitted only a DEBUG registration count, so a plugin
whose required env vars were absent loaded silently (issue NousResearch#2768).

- Normalize requires_env entries (str or dict, matching
  hermes_cli/plugins_cmd.py:307-314) before checking the process
  environment.
- Emit a WARNING when any required env var is not set, naming each
  missing variable.
- The empty-registration note stays DEBUG (not WARNING) to avoid false
  positives for provider-only plugins (e.g. image_gen/fal), whose
  registrations aren't tracked in LoadedPlugin's tools/hooks/middleware/
  commands counts.

Per review, fixes two real bugs in the original port:

1. The missing-env check ran AFTER register_fn(ctx). A plugin that
   reads the missing variable directly during registration raises
   straight into the generic 'Failed to load plugin' handler before
   the specific missing-variable diagnostic is ever reported. Moved
   the check before register_fn(ctx) so the WARNING fires regardless
   of whether registration itself succeeds.

2. The provider-only regression test used a no-op register(), which
   doesn't actually exercise a provider registration. Replaced with a
   real ImageGenProvider subclass registered via
   ctx.register_image_gen_provider(), matching how plugins/image_gen/fal
   actually registers.

Scope note: this fix is in the general PluginManager loader
(hermes_cli/plugins.py). The Hindsight memory plugin path uses a
separate loader (plugins/memory/__init__.py) that general discovery
skips entirely -- this PR does not touch that path.

Added a raising-register() regression test per review, and the
corrected real-provider test. 5/5 new tests pass; 121/121 in the full
tests/hermes_cli/test_plugins.py file.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for carrying the str/dict normalization and the pre-register() diagnostic forward. The core missing-env gap remains on current main (hermes_cli/plugins.py:1783-1811).

Problems

  • The proposed check is still after module import: the PR loads a directory/entry-point module at hermes_cli/plugins.py:1764-1767, then begins validation at hermes_cli/plugins.py:1779. A plugin that accesses a declared variable at module scope raises into the generic loader handler before the specific diagnostic runs.
  • The provider test captures only WARNING records at tests/hermes_cli/test_plugins.py:2533-2540, while the newly added empty-registration message is DEBUG. Its loaded.enabled assertion does not verify that the provider was registered.

Suggested changes

  • Validate and warn before module loading, and add a module-import failure regression.
  • Assert the named test provider is retrievable from agent.image_gen_registry before cleanup.

Automated hermes-sweeper review.

Comment thread hermes_cli/plugins.py Outdated
else:
ctx = PluginContext(manifest, self)

# Warn when a declared required env var is missing, BEFORE

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check runs after _load_directory_module() / _load_entrypoint_module() have executed. Move it before module loading so a plugin whose module-level import accesses a missing declared variable receives this specific diagnostic rather than only the generic loader failure.

assert not any("registered no tools" in m for m in warning_msgs), (
f"False positive warning for provider-only plugin: {warning_msgs}"
)
loaded = mgr._plugins.get("provider_only")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loaded.enabled only proves register() returned normally. Assert agent.image_gen_registry.get_provider('test-provider-2768') is non-null so this regression verifies the claimed provider-registration path; the WARNING assertion alone is vacuous because the new message is DEBUG.

…al provider registration

Follow-up per review of NousResearch#68703.

Two real gaps:

1. The missing-env check still ran after
   _load_directory_module()/_load_entrypoint_module() -- only before
   register_fn(ctx). A plugin that accesses a declared requires_env
   variable at MODULE scope (not just inside register()) still raises
   during the import itself, propagating straight to the generic
   "Failed to load plugin" handler before the specific diagnostic ever
   runs. Moved the check to run first, using manifest.requires_env
   directly (available before the module is ever imported, so no
   ordering dependency on the module existing at all).

2. The provider-only regression test's caplog capture only recorded
   WARNING records, while the empty-registration message it was
   guarding against is DEBUG -- so the WARNING-absence assertion never
   actually exercised anything, and loaded.enabled only proves
   register() returned without raising, not that the registration call
   inside it took effect. Added an assertion that the provider is
   actually retrievable via agent.image_gen_registry.get_provider(),
   which only succeeds if ctx.register_image_gen_provider() genuinely
   ran.

Added the requested module-import-failure regression test: a plugin
that raises accessing the missing variable at module scope (before
register() is even an attribute on the module) must still produce the
specific WARNING, proving the check now runs early enough to catch
that case too.

122/122 tests pass in the full tests/hermes_cli/test_plugins.py file
(2 new/fixed, no regression).
@ygd58

ygd58 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

Both were real. Fixed in the latest commit:

  1. Moved the missing-env check to run before _load_directory_module()/_load_entrypoint_module() entirely, using manifest.requires_env directly (no dependency on the module having been imported). A plugin that accesses a missing declared variable at module scope now gets the specific WARNING too, not just the generic loader failure.
  2. The provider-only test now asserts agent.image_gen_registry.get_provider(...) returns non-null, not just loaded.enabled -- proving register_image_gen_provider() actually ran.

Added the requested module-import-failure regression test. 122/122 pass in the full tests/hermes_cli/test_plugins.py file (no regression).

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 30, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

Ten PRs address or reference this two-issue complex. The diffs divide into generic plugin-loader diagnostics (#2768, #58692, #63050, #68703), systemd environment loading (#18606), unavailable-memory-provider diagnostics (#70257), and lifecycle-hook implementations for closed #2817 (#2820, #2823, #2930, #3542).

Related pull requests

Duplicates

#2768, #58692, #63050, and #68703 substantially overlap on generic required-env diagnostics; #63050 is explicitly superseded by #68703, while #58692 is a salvage of #2768. #2820, #2823, #2930, and #3542 overlap on lifecycle hooks, with merged #3542 as the shipped consolidation. #70257 is complementary, not a duplicate, because it changes the separate memory-provider availability path.

Suggested consolidation

Author action: rebase #2768 onto main, or split out the part that can close the generic-loader gap, using #68703's pre-import str/dict validation and registry-backed regressions while retaining any complete registration-surface accounting from #58692. This preserves #2768's recorded best-fix designation and addresses its keep_open verdict; keep #68703 open as the concrete salvage path until that transfer is complete, then #58692 and #68703 can close as duplicates of #2768, while closed #70257 remains preserved through successor #74379 and the lifecycle PRs remain consolidated in merged #3542.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I2765(["issue #2765 (open)"])
    subgraph Dup63050 ["PRs duplicating each other"]
        P63050["PR #63050 (closed)"]
        P68703["PR #68703 (open)"]
    end
    P68703 -.->|partial| I2765
    class I2765 open
    class P63050 closed
    class P68703 open
    class P68703 target
    click I2765 "https://github.com/NousResearch/hermes-agent/issues/2765"
    click P63050 "https://github.com/NousResearch/hermes-agent/pull/63050"
    click P68703 "https://github.com/NousResearch/hermes-agent/pull/68703"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 10 pull requests and 2 issues in this complex. Each diff was read against this issue; Assessment working set: 109 kB of PR diffs, 29 kB of issue/PR text, 20 kB of discussion (27 comments), 14 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants