Skip to content

fix(cli): prevent plugin loading logs from polluting 'openclaw agent --json' stdout#51086

Closed
tomi-claycosmos wants to merge 1 commit intoopenclaw:mainfrom
tomi-claycosmos:fix/agent-json-plugin-logs
Closed

fix(cli): prevent plugin loading logs from polluting 'openclaw agent --json' stdout#51086
tomi-claycosmos wants to merge 1 commit intoopenclaw:mainfrom
tomi-claycosmos:fix/agent-json-plugin-logs

Conversation

@tomi-claycosmos
Copy link
Copy Markdown

Summary

When running openclaw agent --json, plugin loading logs were being printed to stdout, corrupting the JSON output.

Root cause: The agentCliCommand function did not redirect runtime logs to stderr when --json was used. This is the same class of bug that was already fixed in completion-cli.ts (see the routeLogsToStderr() call there).

Fix: Call routeLogsToStderr() at the start of agentCliCommand when opts.json is true. This redirects all console.log / defaultRuntime.log calls to stderr, keeping stdout clean for the pure JSON response.

Changes

  • src/commands/agent-via-gateway.ts: Import routeLogsToStderr and call it when opts.json is true

Testing

openclaw agent --json --message "hello" --to +15555550123
# Before: stdout was polluted with plugin loading messages
# After: stdout contains only clean JSON

Fixes #51076

@openclaw-barnacle openclaw-barnacle Bot added commands Command implementations size: XS labels Mar 20, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 20, 2026

Greptile Summary

This PR attempts to fix stdout pollution from plugin loading logs when running openclaw agent --json by calling routeLogsToStderr() at the start of agentCliCommand. The intent is correct — routeLogsToStderr() is already used successfully in completion-cli.ts for the same class of problem — but the implementation has a critical flaw that makes --json output completely broken.

  • Root cause of the new bug: By the time agentCliCommand executes, enableConsoleCapture() has already patched console.log in run-main.ts. Once routeLogsToStderr() sets forceConsoleToStderr = true, all subsequent console.log calls — including the one that outputs the final JSON payload in agentViaGatewayCommand (runtime.log(JSON.stringify(response, null, 2))) — are redirected to process.stderr. The result: stdout is empty and the JSON appears on stderr.
  • Why completion-cli.ts avoids this: It writes its output with process.stdout.write(script + "\n"), which bypasses the patched console.log entirely.
  • Recommended fix: Change the JSON output in agentViaGatewayCommand from runtime.log(JSON.stringify(...)) to process.stdout.write(JSON.stringify(...) + "\n") so it is not subject to the stderr redirect.

Confidence Score: 1/5

  • This PR introduces a regression: openclaw agent --json will produce empty stdout and JSON on stderr, making the flag non-functional.
  • The fix correctly identifies that routeLogsToStderr() should be called, but the final JSON output in agentViaGatewayCommand is still emitted via runtime.logconsole.log, which is already intercepted by enableConsoleCapture. With forceConsoleToStderr = true, the JSON goes to stderr. This is a clear regression — the --json flag will produce no output on stdout after the fix.
  • src/commands/agent-via-gateway.ts — specifically the runtime.log(JSON.stringify(response, null, 2)) call in agentViaGatewayCommand (around line 160) needs to be changed to process.stdout.write.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/commands/agent-via-gateway.ts
Line: 184-186

Comment:
**JSON response redirected to stderr by same call**

Calling `routeLogsToStderr()` here patches `console.log` (via `enableConsoleCapture`, which runs before this point in `run-main.ts`) to write to `process.stderr` whenever `forceConsoleToStderr` is `true`. The problem is that the final JSON payload in `agentViaGatewayCommand` is emitted the same way:

```ts
// agentViaGatewayCommand (line ~160)
if (opts.json) {
  runtime.log(JSON.stringify(response, null, 2));  // calls console.log → now goes to stderr!
  return response;
}
```

`runtime.log` delegates to `console.log`, which is already patched. Once `routeLogsToStderr()` is called, *every* subsequent `console.log` call — including the one that outputs the JSON result — is redirected to `process.stderr`. This means `--json` stdout would be empty and the JSON would land on stderr instead.

Compare with `completion-cli.ts`, which writes its stdout output with `process.stdout.write(script + "\n")` directly, bypassing the console patch entirely.

The fix should write the JSON using `process.stdout.write` (or `rawConsole.log` / `loggingState.rawConsole`) so it is not subject to the `forceConsoleToStderr` redirect. For example, in `agentViaGatewayCommand`:

```ts
if (opts.json) {
  process.stdout.write(JSON.stringify(response, null, 2) + "\n");
  return response;
}
```

Without this change, `openclaw agent --json` will produce an empty stdout and the JSON on stderr — the opposite of the intended behaviour.

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

Last reviewed commit: "fix(cli): route logs..."

Comment on lines +184 to +186
if (opts.json) {
routeLogsToStderr();
}
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.

P1 JSON response redirected to stderr by same call

Calling routeLogsToStderr() here patches console.log (via enableConsoleCapture, which runs before this point in run-main.ts) to write to process.stderr whenever forceConsoleToStderr is true. The problem is that the final JSON payload in agentViaGatewayCommand is emitted the same way:

// agentViaGatewayCommand (line ~160)
if (opts.json) {
  runtime.log(JSON.stringify(response, null, 2));  // calls console.log → now goes to stderr!
  return response;
}

runtime.log delegates to console.log, which is already patched. Once routeLogsToStderr() is called, every subsequent console.log call — including the one that outputs the JSON result — is redirected to process.stderr. This means --json stdout would be empty and the JSON would land on stderr instead.

Compare with completion-cli.ts, which writes its stdout output with process.stdout.write(script + "\n") directly, bypassing the console patch entirely.

The fix should write the JSON using process.stdout.write (or rawConsole.log / loggingState.rawConsole) so it is not subject to the forceConsoleToStderr redirect. For example, in agentViaGatewayCommand:

if (opts.json) {
  process.stdout.write(JSON.stringify(response, null, 2) + "\n");
  return response;
}

Without this change, openclaw agent --json will produce an empty stdout and the JSON on stderr — the opposite of the intended behaviour.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/agent-via-gateway.ts
Line: 184-186

Comment:
**JSON response redirected to stderr by same call**

Calling `routeLogsToStderr()` here patches `console.log` (via `enableConsoleCapture`, which runs before this point in `run-main.ts`) to write to `process.stderr` whenever `forceConsoleToStderr` is `true`. The problem is that the final JSON payload in `agentViaGatewayCommand` is emitted the same way:

```ts
// agentViaGatewayCommand (line ~160)
if (opts.json) {
  runtime.log(JSON.stringify(response, null, 2));  // calls console.log → now goes to stderr!
  return response;
}
```

`runtime.log` delegates to `console.log`, which is already patched. Once `routeLogsToStderr()` is called, *every* subsequent `console.log` call — including the one that outputs the JSON result — is redirected to `process.stderr`. This means `--json` stdout would be empty and the JSON would land on stderr instead.

Compare with `completion-cli.ts`, which writes its stdout output with `process.stdout.write(script + "\n")` directly, bypassing the console patch entirely.

The fix should write the JSON using `process.stdout.write` (or `rawConsole.log` / `loggingState.rawConsole`) so it is not subject to the `forceConsoleToStderr` redirect. For example, in `agentViaGatewayCommand`:

```ts
if (opts.json) {
  process.stdout.write(JSON.stringify(response, null, 2) + "\n");
  return response;
}
```

Without this change, `openclaw agent --json` will produce an empty stdout and the JSON on stderr — the opposite of the intended behaviour.

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7caf1239ad

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +184 to +185
if (opts.json) {
routeLogsToStderr();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep openclaw agent --json output on stdout

In the normal CLI entrypoint, src/cli/run-main.ts:113-115 enables console capture, and defaultRuntime.log is implemented with console.log (src/runtime.ts:21-29). routeLogsToStderr() flips all captured console.log output to stderr (src/logging/console.ts:115-117 and src/logging/console.ts:291-296), so this new call also redirects the later runtime.log(JSON.stringify(...)) in agentViaGatewayCommand (src/commands/agent-via-gateway.ts:159-161). That means openclaw agent --json now produces its JSON response on stderr instead of stdout, breaking the machine-readable contract for real CLI users even though the completion command pattern looked similar.

Useful? React with 👍 / 👎.

@tomi-claycosmos
Copy link
Copy Markdown
Author

Noted. The concern about JSON output going to stderr via routeLogsToStderr() is valid — I will switch to process.stdout.write for structured JSON to preserve the --json contract.

@steipete
Copy link
Copy Markdown
Contributor

Closing this as implemented after Codex review.

Current main already keeps --json CLI startup/plugin logs off stdout and writes openclaw agent --json payloads through the runtime JSON writer, so this PR’s targeted change is obsolete.

What I checked:

  • Generic JSON-mode stderr routing already exists: CLI preaction detects JSON output mode and applies startup presentation before command execution; startup presentation routes logs to stderr when stdout must stay clean. (src/cli/program/preaction.ts:71, 6944d7025d9c)
  • Bootstrap passes suppression into plugin loading: Command bootstrap forwards suppressDoctorStdout into plugin registry loading, so plugin-loading logs are redirected during JSON-mode startup instead of leaking to stdout. (src/cli/command-bootstrap.ts:14, 6944d7025d9c)
  • Plugin loader scopes and restores stderr routing: Plugin registry loading saves and restores loggingState.forceConsoleToStderr, so the stderr redirect applies during plugin load but does not permanently hijack later command output. (src/cli/plugin-registry-loader.ts:18, 6944d7025d9c)
  • Agent JSON output uses runtime JSON writer: agentViaGatewayCommand now emits JSON via writeRuntimeJson(runtime, response) rather than plain runtime.log(...), avoiding the regression called out in review. (src/commands/agent-via-gateway.ts:157, 6944d7025d9c)
  • Runtime JSON writer goes to stdout for output-capable runtimes: writeRuntimeJson uses runtime.writeJson, and the default/output runtime implementation writes with process.stdout.write, preserving the stdout JSON contract even when console logs are routed to stderr. (src/runtime.ts:52, 6944d7025d9c)
  • Regression tests cover the exact class of bug: Tests assert that --json mode routes logs to stderr so stdout stays clean, and specifically that plugin loading is routed to stderr during JSON mode and restored afterward. (src/cli/program/preaction.test.ts:394, 6944d7025d9c)

So I’m closing this as already implemented rather than keeping a duplicate issue open.

Review notes: reviewed against 6944d7025d9c; fix evidence: release v2026.4.22, commit 00bd2cf7a376.

@steipete steipete closed this Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands Command implementations size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

openclaw agent --json output polluted by plugin loading logs

3 participants