Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

fix: suppress notifier plugin warnings from CLI output#1684

Open
i-trytoohard wants to merge 1 commit intomainComposioHQ/agent-orchestrator:mainfrom
fix/notifier-log-spam-1681ComposioHQ/agent-orchestrator:fix/notifier-log-spam-1681Copy head branch name to clipboard
Open

fix: suppress notifier plugin warnings from CLI output#1684
i-trytoohard wants to merge 1 commit intomainComposioHQ/agent-orchestrator:mainfrom
fix/notifier-log-spam-1681ComposioHQ/agent-orchestrator:fix/notifier-log-spam-1681Copy head branch name to clipboard

Conversation

@i-trytoohard
Copy link
Copy Markdown
Collaborator

Problem

Notifier plugins (notifier-discord, notifier-slack, notifier-openclaw, notifier-webhook, notifier-composio, notifier-desktop) emit unconditional console.warn() during their create() initialization when config is missing (no webhook URL, no token, unsupported OS).

Since getPluginRegistry() is called by almost every CLI command, these warnings spam the terminal on every invocation — even commands that have nothing to do with notifications.

Affected commands (all of these trigger the spam)

ao status, ao session ls, ao session show, ao session kill, ao session restore, ao session claim-pr, ao session detail, ao session cleanup, ao spawn, ao send, ao start, ao stop, ao review-check, ao report, ao verify, ao plugin list, ao plugin install

Example spam on every ao status run

[notifier-slack] No webhookUrl configured — notifications will be no-ops
[notifier-discord] No webhookUrl configured. Set it in agent-orchestrator.yaml...
[notifier-webhook] No url configured — notifications will be no-ops
[notifier-openclaw] No token configured. Set OPENCLAW_HOOKS_TOKEN env var...
[notifier-composio] No composioApiKey or COMPOSIO_API_KEY configured — notifications will be no-ops
[notifier-desktop] Desktop notifications not supported on linux

Fix

Add suppressNotifierWarnings option (default: true) to createPluginRegistry(). When enabled, console.warn is temporarily silenced during notifier plugin create() calls via a withSuppressedWarn() wrapper.

Only ao doctor disables suppression (suppressNotifierWarnings: false), so warnings appear where they belong — during diagnostics.

Files changed

  • packages/core/src/plugin-registry.ts — Added CreatePluginRegistryOptions interface and withSuppressedWarn() helper. All registerNotifier() calls now route through it.
  • packages/core/src/index.ts — Export CreatePluginRegistryOptions type.
  • packages/cli/src/commands/doctor.ts — Creates registry with suppressNotifierWarnings: false.
  • packages/cli/src/commands/plugin.ts — Removes manual console.warn suppression hack, uses the new option instead.

Tests

  • plugin-registry.test.ts: 37/37 pass ✅
  • doctor.test.ts: 10/10 pass ✅
  • plugin.test.ts: 6/6 pass ✅
  • Full pnpm build: clean ✅

Closes #1681

Notifier plugins emit console.warn() during create() when config is
missing (no webhook URL, no token, etc). Since getPluginRegistry() is
called by almost every CLI command, these warnings spam the terminal
on every invocation — even for commands unrelated to notifications.

Add suppressNotifierWarnings option (default: true) to
createPluginRegistry(). When enabled, console.warn is temporarily
silenced during notifier plugin create() calls.

Only ao doctor disables suppression, so warnings appear where they
belong — during diagnostics.

Fixes #1681

Affected commands (no longer spam):
  ao status, ao session ls/show/kill/restore/claim-pr/detail/cleanup,
  ao spawn, ao send, ao start, ao stop, ao review-check, ao report,
  ao verify, ao plugin list/install
@i-trytoohard i-trytoohard requested a review from yyovil May 6, 2026 14:21
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

Test Coverage Report

Metric Value
Lines covered 710/1048
Lines not covered 338/1048
Overall coverage 67.7%
Per-file breakdown
File Coverage
packages/cli/src/commands/doctor.ts 236/351 (67.2%)
packages/cli/src/commands/plugin.ts 326/518 (62.9%)
packages/core/src/plugin-registry.ts 148/179 (82.7%)

Uncovered lines

  • packages/cli/src/commands/doctor.ts: L165-L167, L196-L200, L202-L205, L207-L221, L223-L231, L233-L235, L238-L240, L242-L254, L256-L257, L260-L271, L273-L277, L279-L284, L286-L291, L293-L294, L305-L307, L311-L312, L341-L342, L346-L348, L357-L359, L376-L378, L428-L430, L445-L447, L454-L456, L459-L460
  • packages/cli/src/commands/plugin.ts: L44, L66, L83-L86, L99-L100, L113-L114, L128-L142, L147-L150, L155-L156, L167-L170, L176-L184, L209-L215, L245-L248, L262-L267, L269-L278, L280-L288, L293-L301, L349-L351, L401-L402, L404-L422, L424-L434, L436-L447, L449-L459, L461-L473, L511-L512, L532-L535, L547-L548, L551-L552, L557, L561-L566, L578-L579, L605-L608, L610-L613, L615-L617, L619-L620
  • packages/core/src/plugin-registry.ts: L133, L201, L289-L290, L292-L297, L300-L301, L303-L304, L306, L316, L334-L335, L339-L340, L352-L354, L357, L361-L363, L381, L517-L518, L552

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 6, 2026

Greptile Summary

This PR centralises the console.warn suppression that existed as a one-off hack in plugin.ts into the core createPluginRegistry factory, making warning suppression the default for all callers and adding an explicit opt-out for ao doctor.

  • plugin-registry.ts gains a withSuppressedWarn helper that temporarily replaces console.warn with a no-op around every notifier plugin.create() call, along with the CreatePluginRegistryOptions interface.
  • doctor.ts passes suppressNotifierWarnings: false to preserve diagnostic output; plugin.ts drops its local suppression hack.
  • Several other callers (verify.ts, create-session-manager.ts, web/src/lib/services.ts) silently inherit the true default without being mentioned in the PR.

Confidence Score: 4/5

Safe to merge; the change correctly solves the warning spam problem and is well-scoped to notifier initialization paths.

The core helper monkey-patches a global (console.warn) without a type guard against async usage, and the suppression can theoretically swallow unrelated third-party warnings or interleave unexpectedly in parallel test processes. These are non-blocking in the current codebase where all notifier create() calls are synchronous, but they add latent fragility to plugin-registry.ts.

packages/core/src/plugin-registry.ts — the withSuppressedWarn helper and its global-state mutation deserve a second look before this pattern is reused.

Important Files Changed

Filename Overview
packages/core/src/plugin-registry.ts Adds CreatePluginRegistryOptions interface and withSuppressedWarn helper that monkey-patches global console.warn during each plugin.create() call; suppression is synchronous-only and unconstrained by TypeScript, which can be a hazard if async plugins are introduced or tests run in parallel.
packages/cli/src/commands/doctor.ts Opts out of warning suppression with suppressNotifierWarnings: false so diagnostic output remains visible; minimal, correct change.
packages/cli/src/commands/plugin.ts Removes the local try/finally console.warn suppression hack and delegates to the new centralised option; cleaner and equivalent.
packages/core/src/index.ts Re-exports the new CreatePluginRegistryOptions type; trivial, no issues.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI Command
    participant Registry as createPluginRegistry()
    participant Suppressor as withSuppressedWarn()
    participant Global as console.warn (global)
    participant Plugin as notifier plugin.create()

    CLI->>Registry: createPluginRegistry({ suppressNotifierWarnings: true/false })
    Registry->>Registry: loadFromConfig() → loadBuiltins()
    Registry->>Suppressor: withSuppressedWarn(() => plugin.create(config))
    alt suppressNotifierWarnings = true
        Suppressor->>Global: console.warn = noop
        Suppressor->>Plugin: plugin.create(config)
        Plugin-->>Suppressor: instance
        Suppressor->>Global: console.warn = original (finally)
    else suppressNotifierWarnings = false (ao doctor)
        Suppressor->>Plugin: plugin.create(config)
        Plugin->>Global: console.warn notifier warning
        Plugin-->>Suppressor: instance
    end
    Suppressor-->>Registry: instance
    Registry-->>CLI: PluginRegistry
Loading

Comments Outside Diff (1)

  1. packages/cli/src/commands/verify.ts, line 60 (link)

    P2 Silent adoption of warning suppression via default

    verify.ts (and similarly create-session-manager.ts and packages/web/src/lib/services.ts) calls createPluginRegistry() with no arguments and will inherit suppressNotifierWarnings: true from the new default. Consider passing { suppressNotifierWarnings: true } explicitly at these sites so the intent is self-documenting — similar to how plugin.ts was updated — rather than relying on a default that could change.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: packages/cli/src/commands/verify.ts
    Line: 60
    
    Comment:
    **Silent adoption of warning suppression via default**
    
    `verify.ts` (and similarly `create-session-manager.ts` and `packages/web/src/lib/services.ts`) calls `createPluginRegistry()` with no arguments and will inherit `suppressNotifierWarnings: true` from the new default. Consider passing `{ suppressNotifierWarnings: true }` explicitly at these sites so the intent is self-documenting — similar to how `plugin.ts` was updated — rather than relying on a default that could change.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
packages/core/src/plugin-registry.ts:414-423
**Global `console.warn` mutation swallows unrelated warnings**

`withSuppressedWarn` replaces `console.warn` on the global `console` object for the duration of each `plugin.create()` call. Any third-party library code that happens to call `console.warn` synchronously inside `create()` — not just the notifier's own misconfiguration messages — will also be silently dropped. In test environments running multiple `createPluginRegistry` instances concurrently (e.g., parallel test workers sharing the same Node.js process), two calls entering `withSuppressedWarn` in an interleaved fashion can leave `console.warn` permanently silenced if the inner call restores a no-op as `original`.

### Issue 2 of 3
packages/core/src/plugin-registry.ts:414
**`withSuppressedWarn` type allows async `fn` but suppression ends synchronously**

The generic `T` is unconstrained, so TypeScript happily accepts an async lambda: `withSuppressedWarn(() => asyncCreate(config))`. In that case the `finally` block restores `console.warn` as soon as the Promise is returned — before the async body runs — making the suppression a no-op for async plugins. A compile-time guard prevents this category of silent breakage.

```suggestion
  function withSuppressedWarn<T extends Exclude<unknown, Promise<unknown>>>(fn: () => T): T {
```

### Issue 3 of 3
packages/cli/src/commands/verify.ts:60
**Silent adoption of warning suppression via default**

`verify.ts` (and similarly `create-session-manager.ts` and `packages/web/src/lib/services.ts`) calls `createPluginRegistry()` with no arguments and will inherit `suppressNotifierWarnings: true` from the new default. Consider passing `{ suppressNotifierWarnings: true }` explicitly at these sites so the intent is self-documenting — similar to how `plugin.ts` was updated — rather than relying on a default that could change.

Reviews (1): Last reviewed commit: "fix: suppress notifier plugin warnings f..." | Re-trigger Greptile

Comment on lines +414 to +423
function withSuppressedWarn<T>(fn: () => T): T {
if (!suppressNotifierWarnings) return fn();
const original = console.warn;
console.warn = () => {};
try {
return fn();
} finally {
console.warn = original;
}
}
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.

P2 Global console.warn mutation swallows unrelated warnings

withSuppressedWarn replaces console.warn on the global console object for the duration of each plugin.create() call. Any third-party library code that happens to call console.warn synchronously inside create() — not just the notifier's own misconfiguration messages — will also be silently dropped. In test environments running multiple createPluginRegistry instances concurrently (e.g., parallel test workers sharing the same Node.js process), two calls entering withSuppressedWarn in an interleaved fashion can leave console.warn permanently silenced if the inner call restores a no-op as original.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/src/plugin-registry.ts
Line: 414-423

Comment:
**Global `console.warn` mutation swallows unrelated warnings**

`withSuppressedWarn` replaces `console.warn` on the global `console` object for the duration of each `plugin.create()` call. Any third-party library code that happens to call `console.warn` synchronously inside `create()` — not just the notifier's own misconfiguration messages — will also be silently dropped. In test environments running multiple `createPluginRegistry` instances concurrently (e.g., parallel test workers sharing the same Node.js process), two calls entering `withSuppressedWarn` in an interleaved fashion can leave `console.warn` permanently silenced if the inner call restores a no-op as `original`.

How can I resolve this? If you propose a fix, please make it concise.

* Used to suppress noisy notifier misconfiguration warnings that
* should only appear during `ao doctor` diagnostics.
*/
function withSuppressedWarn<T>(fn: () => T): T {
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.

P2 withSuppressedWarn type allows async fn but suppression ends synchronously

The generic T is unconstrained, so TypeScript happily accepts an async lambda: withSuppressedWarn(() => asyncCreate(config)). In that case the finally block restores console.warn as soon as the Promise is returned — before the async body runs — making the suppression a no-op for async plugins. A compile-time guard prevents this category of silent breakage.

Suggested change
function withSuppressedWarn<T>(fn: () => T): T {
function withSuppressedWarn<T extends Exclude<unknown, Promise<unknown>>>(fn: () => T): T {
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/core/src/plugin-registry.ts
Line: 414

Comment:
**`withSuppressedWarn` type allows async `fn` but suppression ends synchronously**

The generic `T` is unconstrained, so TypeScript happily accepts an async lambda: `withSuppressedWarn(() => asyncCreate(config))`. In that case the `finally` block restores `console.warn` as soon as the Promise is returned — before the async body runs — making the suppression a no-op for async plugins. A compile-time guard prevents this category of silent breakage.

```suggestion
  function withSuppressedWarn<T extends Exclude<unknown, Promise<unknown>>>(fn: () => T): T {
```

How can I resolve this? If you propose a fix, please make it concise.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Notifier plugin warnings spam stdout/stderr on every CLI command

1 participant

Morty Proxy This is a proxified and sanitized view of the page, visit original site.