openclaw - ✅(Solved) Fix Plugin init logs leak to stdout in CLI subcommands (message send, etc.) [2 pull requests, 3 comments, 4 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#51496Fetched 2026-04-08 01:10:24
View on GitHub
Comments
3
Participants
4
Timeline
8
Reactions
0
Timeline (top)
commented ×3cross-referenced ×3referenced ×2

Error Message

Scripts that capture openclaw message send output to check for error conditions get polluted with plugin init noise:

Fix Action

Workaround

A filter wrapper is being used locally:

openclaw message send "$@" \
  2> >(grep -v '^\[plugins\]' >&2) \
  | grep -v '^\[plugins\]'

PR fix notes

PR #51641: fix(cli): route diagnostic logs to stderr when stdout is piped

Description (problem / solution / changelog)

Summary

  • Problem: Plugin init logs ([plugins] lines) are written to stdout instead of stderr, contaminating captured output in scripts that use $(openclaw message send ...) or pipe the CLI output.
  • Root cause: writeConsoleLine() in the subsystem logger routes info-level diagnostic messages through console.log (stdout), even when stdout is piped/captured.
  • Fix: In writeConsoleLine(), route info/debug/trace-level subsystem diagnostic logs to stderr when !process.stdout.isTTY. This is scoped to the subsystem logger sink only — command data output via runtime.log() / console.log() is unaffected.
  • Bonus: Version output in help.ts now uses process.stdout.write() directly since it's data, not a diagnostic log.

Closes #51496

Change type

  • Bug fix

Scope

  • Gateway / CLI

How it works

There are two separate output paths in the CLI:

  1. Command data: runtime.log()console.log()forward() wrapper → stdout (unchanged)
  2. Diagnostic logs: createSubsystemLogger()writeConsoleLine()rawConsole.logstdout (this is the bug)

The fix adds a TTY check in writeConsoleLine() (src/logging/subsystem.ts):

} else if (!process.stdout.isTTY) {
  // When stdout is piped/captured, route subsystem diagnostic logs to stderr
  (sink.error ?? console.error)(sanitized);
} else {
  (sink.log ?? console.log)(sanitized);
}

This only affects subsystem logger output. --version, --json, and all runtime.log() command output stays on stdout.

Test plan

  • New test: subsystem logger routes info-level logs to stderr when stdout is not a TTY
  • New test: subsystem logger routes info-level logs to stdout when stdout is a TTY
  • Updated test: version output now asserts process.stdout.write() instead of console.log
  • All existing subsystem.test.ts tests pass (13 total)
  • All existing console-capture.test.ts tests pass (11 total)
  • All existing run-main.test.ts tests pass (15 total)
  • All existing help.test.ts tests pass (4 total)
  • pnpm tsgo passes

[!NOTE] This PR was AI-assisted using Claude (Opus 4.6) via Claude Code. All code has been reviewed and tested locally by a human.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) [email protected]

Changed files

  • src/cli/program/help.test.ts (modified, +3/-3)
  • src/cli/program/help.ts (modified, +4/-3)
  • src/logging/subsystem.test.ts (modified, +44/-0)
  • src/logging/subsystem.ts (modified, +4/-0)

PR #52008: CLI: write plugin bootstrap logs to stderr (#51496)

Description (problem / solution / changelog)

Summary

While loadOpenClawPlugins runs during route-first startup, set loggingState.forceConsoleToStderr so [plugins] lines use stderr instead of stdout (keeps `` captures clean).

Test plan

  • pnpm test -- src/cli/plugin-registry.test.ts

Fixes #51496

Made with Cursor

Changed files

  • src/cli/plugin-registry.ts (modified, +24/-17)

Code Example

$ openclaw message send --channel whatsapp --target "+31627894125" --message "test" --dry-run >/tmp/stdout.txt 2>/tmp/stderr.txt
$ cat /tmp/stdout.txt
[plugins] graphiti-kg: registered (url: http://127.0.0.1:8000, group: openclaw-main)
[plugins] memory-lancedb-pro@1.0.32: plugin registered (db: /home/braminator/.openclaw/memory/lancedb-pro, model: text-embedding-3-small)
[plugins] mdMirror: no agent workspaces found, writes will use fallback dir: /home/braminator/.openclaw/workspace/memory-md
[plugins] graphiti-knowledge-graph: loaded without install/load-path provenance; treat as untracked local code and pin trust via plugins.allow or install records (/home/braminator/.openclaw/extensions/graphiti-knowledge-graph/index.ts)
[plugins] memory-lancedb-pro: loaded without install/load-path provenance; treat as untracked local code and pin trust via plugins.allow or install records (/home/braminator/.openclaw/extensions/memory-lancedb-pro/index.ts)
[plugins] graphiti-kg: registered (url: http://127.0.0.1:8000, group: openclaw-main)
[plugins] memory-lancedb-pro@1.0.32: plugin registered (db: /home/braminator/.openclaw/memory/lancedb-pro, model: text-embedding-3-small)
[plugins] mdMirror: no agent workspaces found, writes will use fallback dir: /home/braminator/.openclaw/workspace/memory-md

---

probe=$(openclaw message send ... --dry-run 2>&1)
# $probe now contains [plugins] lines + actual result

---

openclaw message send "$@" \
  2> >(grep -v '^\[plugins\]' >&2) \
  | grep -v '^\[plugins\]'

---

OpenClaw 2026.3.13 (61d171a)
RAW_BUFFERClick to expand / collapse

Problem

When running openclaw message send, plugin initialization logs ([plugins] lines) are printed to stdout instead of stderr. This contaminates any script that captures output via $(...) or pipes.

Evidence

$ openclaw message send --channel whatsapp --target "+31627894125" --message "test" --dry-run >/tmp/stdout.txt 2>/tmp/stderr.txt
$ cat /tmp/stdout.txt
[plugins] graphiti-kg: registered (url: http://127.0.0.1:8000, group: openclaw-main)
[plugins] [email protected]: plugin registered (db: /home/braminator/.openclaw/memory/lancedb-pro, model: text-embedding-3-small)
[plugins] mdMirror: no agent workspaces found, writes will use fallback dir: /home/braminator/.openclaw/workspace/memory-md
[plugins] graphiti-knowledge-graph: loaded without install/load-path provenance; treat as untracked local code and pin trust via plugins.allow or install records (/home/braminator/.openclaw/extensions/graphiti-knowledge-graph/index.ts)
[plugins] memory-lancedb-pro: loaded without install/load-path provenance; treat as untracked local code and pin trust via plugins.allow or install records (/home/braminator/.openclaw/extensions/memory-lancedb-pro/index.ts)
[plugins] graphiti-kg: registered (url: http://127.0.0.1:8000, group: openclaw-main)
[plugins] [email protected]: plugin registered (db: /home/braminator/.openclaw/memory/lancedb-pro, model: text-embedding-3-small)
[plugins] mdMirror: no agent workspaces found, writes will use fallback dir: /home/braminator/.openclaw/workspace/memory-md

The [plugins] lines above all appear on stdout (not stderr), mixed with the actual result.

Impact

Scripts that capture openclaw message send output to check for error conditions get polluted with plugin init noise:

probe=$(openclaw message send ... --dry-run 2>&1)
# $probe now contains [plugins] lines + actual result

Expected Behavior

Plugin initialization logs should go to stderr (or be suppressed entirely when --quiet / not a TTY). Stdout should only contain the actual command result.

Workaround

A filter wrapper is being used locally:

openclaw message send "$@" \
  2> >(grep -v '^\[plugins\]' >&2) \
  | grep -v '^\[plugins\]'

Affected Commands

  • openclaw message send
  • Likely all openclaw subcommands that load plugins

Version

OpenClaw 2026.3.13 (61d171a)

Suggested Fix

In the plugin loader, check if stdout is a TTY or if a --quiet flag is set before emitting [plugins] init messages. Otherwise, route them to stderr unconditionally.

extent analysis

Fix Plan

To fix the issue, we need to modify the plugin loader to route [plugins] init messages to stderr instead of stdout. We can achieve this by checking if stdout is a TTY or if a --quiet flag is set.

Step-by-Step Solution

  • Check if stdout is a TTY using the isatty function.
  • Check if a --quiet flag is set.
  • If either condition is true, route [plugins] init messages to stderr.

Example Code

import sys

# ...

def load_plugins():
    # ...
    if not sys.stdout.isatty() or args.quiet:
        # Route [plugins] init messages to stderr
        print(f"[plugins] {message}", file=sys.stderr)
    else:
        # Print [plugins] init messages to stdout
        print(f"[plugins] {message}")
    # ...

Verification

To verify that the fix worked, run the openclaw message send command with the --dry-run flag and redirect stdout to a file:

openclaw message send --channel whatsapp --target "+31627894125" --message "test" --dry-run >/tmp/stdout.txt 2>/tmp/stderr.txt

Then, check the contents of the /tmp/stdout.txt and /tmp/stderr.txt files. The [plugins] init messages should be routed to /tmp/stderr.txt instead of /tmp/stdout.txt.

Extra Tips

  • Make sure to test the fix with different scenarios, such as running the command with and without the --quiet flag.
  • Consider adding a test case to ensure that the fix works as expected.
  • If you're using a logging library, consider using the logging module to handle logging messages instead of printing to stdout/stderr directly.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING