fix(cli): keep route-first json stdout clean

This commit is contained in:
Peter Steinberger
2026-04-27 20:51:19 +01:00
parent f0000ab72d
commit f56897259e
4 changed files with 22 additions and 3 deletions

View File

@@ -16,6 +16,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Channels/Telegram: skip the optional webhook-info API call during polling-mode status checks and startup bot-label probes so long-polling setups avoid an unnecessary Telegram round trip. Carries forward #72990. Thanks @danielgruneberg.
- CLI/models: keep route-first `models status --json` stdout reserved for the JSON payload by routing auth-profile and startup diagnostics to stderr. Fixes #72962. Thanks @vishutdhar.
- Sessions: ignore future-dated session activity timestamps during reset freshness checks and cap future `updatedAt` values at the merge boundary so clock-skewed messages cannot keep stale sessions alive forever. Fixes #72989. Thanks @martingarramon.
- Plugins/CLI: allow managed plugin installs when the active extensions root is a symlink to a real state directory, while keeping nested target symlinks blocked and suppressing misleading hook-pack fallback errors for install-boundary failures. Fixes #72946. Thanks @mayank6136.
- Gateway/startup: keep hot Gateway boot paths on leaf config imports and add max-RSS reporting to the gateway startup bench so low-memory startup regressions are visible before release. Thanks @vincentkoc.

View File

@@ -111,6 +111,10 @@ Options:
- `--probe-max-tokens <n>`
- `--agent <id>` (configured agent id; overrides `OPENCLAW_AGENT_DIR`/`PI_CODING_AGENT_DIR`)
`--json` keeps stdout reserved for the JSON payload. Auth-profile, provider,
and startup diagnostics are routed to stderr so scripts can pipe stdout directly
into tools such as `jq`.
Probe status buckets:
- `ok`

View File

@@ -93,7 +93,7 @@ describe("tryRouteCli", () => {
expect(ensurePluginRegistryLoadedMock).toHaveBeenCalledWith({ scope: "channels" });
});
it("routes logs to stderr during plugin loading in --json mode and restores after", async () => {
it("keeps logs routed to stderr for routed --json commands", async () => {
findRoutedCommandMock.mockReturnValue({
loadPlugins: true,
run: runRouteMock,
@@ -110,7 +110,22 @@ describe("tryRouteCli", () => {
expect(ensurePluginRegistryLoadedMock).toHaveBeenCalled();
expect(captured[0]).toBe(true);
expect(loggingState.forceConsoleToStderr).toBe(false);
expect(loggingState.forceConsoleToStderr).toBe(true);
});
it("routes command-run logs to stderr for config-guard-skipping --json routes", async () => {
const captured: boolean[] = [];
runRouteMock.mockImplementationOnce(async () => {
captured.push(loggingState.forceConsoleToStderr);
return true;
});
await expect(tryRouteCli(["node", "openclaw", "models", "status", "--json"])).resolves.toBe(
true,
);
expect(ensureConfigReadyMock).not.toHaveBeenCalled();
expect(captured).toEqual([true]);
});
it("does not route logs to stderr during plugin loading without --json", async () => {

View File

@@ -23,7 +23,6 @@ async function prepareRoutedCommand(params: {
const { VERSION } = await import("../version.js");
await applyCliExecutionStartupPresentation({
argv: params.argv,
routeLogsToStderrOnSuppress: false,
startupPolicy,
showBanner: process.stdout.isTTY && !startupPolicy.suppressDoctorStdout,
version: VERSION,