Skip to content

fix: route logs to stderr in --json output mode#52063

Open
xl0shk wants to merge 2 commits intoopenclaw:mainfrom
xl0shk:fix/json-stdout-logs
Open

fix: route logs to stderr in --json output mode#52063
xl0shk wants to merge 2 commits intoopenclaw:mainfrom
xl0shk:fix/json-stdout-logs

Conversation

@xl0shk
Copy link
Copy Markdown

@xl0shk xl0shk commented Mar 22, 2026

Fixes #52032

Summary

  • Call routeLogsToStderr() in both CLI execution paths (fast-path routes and Commander preAction hook) when --json is detected, so all log output goes to stderr and stdout stays clean for JSON parsing.

Root Cause

The CLI has two execution paths that both need the fix:

  1. Fast-path (src/cli/route.ts): prepareRoutedCommand() detected --json but only used it for suppressDoctorStdout, never called routeLogsToStderr().
  2. Commander path (src/cli/program/preaction.ts): Same issue — detected --json via isJsonOutputMode() but didn't call routeLogsToStderr().

The existing routeLogsToStderr() function (already used in completion-cli.ts for the same reason) simply sets loggingState.forceConsoleToStderr = true, which the subsystem logger's writeConsoleLine() checks to route output to stderr.

Changes

  • src/cli/route.ts: call routeLogsToStderr() in prepareRoutedCommand() when --json flag is present
  • src/cli/program/preaction.ts: call routeLogsToStderr() in the preAction hook when isJsonOutputMode() returns true
  • Added tests in src/cli/route.test.ts and src/cli/program/preaction.test.ts

Test plan

  • pnpm test -- src/cli/route.test.ts src/cli/program/preaction.test.ts — 15/15 pass
  • pnpm check — all pass

This PR was developed with AI assistance (Claude Code).

@openclaw-barnacle openclaw-barnacle bot added cli CLI command changes size: S labels Mar 22, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 22, 2026

Greptile Summary

This PR fixes a bug where log output could leak to stdout when using --json mode, breaking JSON parsing for callers. The root cause was that routeLogsToStderr() was never invoked in either CLI execution path — the fast-path (src/cli/route.ts) or the Commander preAction hook (src/cli/program/preaction.ts) — even though --json was detected.

Changes:

  • src/cli/route.ts: prepareRoutedCommand() now calls routeLogsToStderr() when hasFlag(argv, "--json") is true, matching the existing suppressDoctorStdout logic.
  • src/cli/program/preaction.ts: The preAction hook now calls routeLogsToStderr() when isJsonOutputMode() returns true. The isJsonOutputMode() function already correctly excludes parse-only commands (e.g., config set) via JSON_PARSE_ONLY_COMMANDS, so those commands are unaffected.
  • Both changed files have corresponding new tests covering the happy path, the non-json path, and the parse-only exclusion.

The fix is minimal and targeted, reusing the existing routeLogsToStderr() function (already used in completion-cli.ts for the same reason) without introducing new state or abstractions.

Confidence Score: 5/5

  • This PR is safe to merge — it applies a focused, well-tested fix to both CLI execution paths with no behavioral regressions.
  • The change is minimal (three lines of production code across two files), directly mirrors the existing completion-cli.ts pattern, and is guarded by the same condition already used for suppressDoctorStdout. Tests cover all relevant cases including the config set parse-only exclusion. No new state, no new abstractions, no side effects beyond the intended stderr routing.
  • No files require special attention.

Last reviewed commit: "CLI: route logs to s..."

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: 959d66c566

ℹ️ 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 147 to +150
const suppressDoctorStdout = isJsonOutputMode(commandPath, argv);
if (suppressDoctorStdout) {
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 Commander --json command output on stdout

runCli() enables console capture before the Commander path is built (src/cli/run-main.ts:113-116), so calling routeLogsToStderr() here flips every later console.log/runtime.log write onto stderr. Many non-routed JSON commands emit their actual machine-readable payload through runtime.log, for example src/commands/channels/list.ts:122-129. After this change, openclaw channels list --json will leave stdout empty and put the JSON on stderr, which breaks any caller that parses stdout.

Useful? React with 👍 / 👎.

Comment on lines 14 to +17
const suppressDoctorStdout = hasFlag(params.argv, "--json");
if (suppressDoctorStdout) {
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.

P2 Badge Apply stderr routing before the fast path executes

In the routed path this call only sets loggingState.forceConsoleToStderr, but runCli() does not call enableConsoleCapture() until after tryRouteCli() has already been skipped (src/cli/run-main.ts:109-114). That means routed commands still use the native console.log/defaultRuntime.log, so textual diagnostics continue to leak to stdout. A concrete case is memory status --json: withMemoryManagerForAgent() can emit defaultRuntime.log(error ?? "Memory search disabled.") in src/cli/memory-cli.ts:138 before the JSON payload at line 425, so stdout is still not parseable JSON in that scenario.

Useful? React with 👍 / 👎.

@xl0shk
Copy link
Copy Markdown
Author

xl0shk commented Mar 22, 2026

The two CI failures are unrelated to this change:

  1. channels — Flaky discord test (monitor.tool-result.sends-status-replies-responseprefix.test.ts) failing on vi.waitFor() timing. This test is in extensions/discord/, untouched by this PR.
  2. install-smoke — Install smoke test, also unrelated to CLI log routing.

All other 30+ checks pass, including the directly relevant check, test (unit shards 1+2), extensions, contracts, build-smoke, and all Windows test shards.

Requesting a re-run of the failed jobs.

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

Labels

cli CLI command changes size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: --json CLI flags output plugin logs to stdout, breaking JSON parsing

1 participant