openclaw - ✅(Solved) Fix perf(cli): agents list --json shouldn't load all extension plugins [1 pull requests, 1 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#71739Fetched 2026-04-26 05:09:01
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×2closed ×1referenced ×1

Root Cause

openclaw agents list --json consistently takes ~7-9s on a fast host and ~11s in a container on 2026.4.23, while peer commands like cron list --all --json, skills list --json, and sessions ... --json stay sub-second on the same install. Programmatic JSON consumers (dashboards, monitoring scripts, IDE plugins) feel this acutely because they poll.

Fix Action

Fix / Workaround

Empirical patch

beforeafter catalog patch
agents list --json warm~9s~5s
agents list (text)~9sunchanged
  • Programmatic callers that poll agents list --json go from ~10s/call to sub-second
  • For agent-dash (the consumer that surfaced this), our /api/dashboard cold path drops from 27s → ~2s and we can drop a 5-min cache TTL workaround we shipped to dodge this
  • Human agents list is unaffected
  • No schema break for callers that consume id, name, model, bindings, isDefault, identity*, workspace, agentDir — all derived from cfg without plugin load

PR fix notes

PR #71746: perf(cli): skip plugin load on agents list --json (#71739)

Description (problem / solution / changelog)

Closes #71739.

Bug

Reporter measured `agents list --json` at ~7-9s on a fast host (~11s in container) on 2026.4.23, while peer `--json` commands stay sub-second:

commandwarmcold
`cron list --all --json`1.0s
`skills list --json`0.9s
`sessions --all-agents --json`1.0s
`channels list --json`0.9s
`agents list --json`8.6s~9s

The reporter's `agent-dash` endpoint cold path dropped from 27s → ~2s after a local dist patch — they could even retire the 5-min cache TTL workaround they shipped to dodge this.

Root cause

`agents list` inherits `loadPlugins: 'always'` from the parent `agents` policy in `command-catalog.ts`, then `agentsListCommand` calls `buildProviderStatusIndex(cfg)` unconditionally. Both paths trigger the bundled-extension import waterfall (~60+ extension `index.js` modules) — but `providerStatus` is only rendered into human text output, never used in JSON.

`channels list` already uses `loadPlugins: 'never'` and proves the shape is right; this PR matches that shape via the safer `text-only` variant so human invocations are unchanged.

Fix (two-line, per reporter's diagnosis)

  1. `src/cli/command-catalog.ts` — opt `agents list` into the existing `text-only` plugin-preload policy. Plugin preload runs for human text output, skips for `--json`.

  2. `src/commands/agents.commands.list.ts` — skip `buildProviderStatusIndex` (and the per-summary provider enrichment loop) when `opts.json`. Provider info is only rendered in human text output via `formatSummary`, so dropping it from JSON has no observable effect on callers that consume `id`, `name`, `model`, `bindings`, `isDefault`, `identity*`, `workspace`, or `agentDir`. `routes` is config-derived and continues to be set in both modes.

Tests

  • new assertion in `command-startup-policy.test.ts`: `agents list` with `jsonOutputMode: true` now resolves to `loadPlugins: false` (was effectively `true` via the parent `agents` "always" policy).
  • existing assertion that human (`jsonOutputMode: false`) still triggers plugin load is preserved verbatim — no behavior change for text mode.

6/6 tests pass. Lint clean (`pnpm oxlint` — 0 warnings, 0 errors).

Out of scope

  • `--bindings` flag opt-in for restoring `providers` in JSON output: worth adding later if any consumer needs it. Reporter said dashboard consumers don't, and `--bindings` already gates `bindingDetails` enrichment, so the precedent is there if needed.
  • Broader plugin-discovery cache work (#67040, #71690) addresses the same family of cold-start cost.

🤖 generated with assistance from Claude Code Co-authored-by: HCL [email protected]

Changed files

  • src/cli/command-catalog.ts (modified, +6/-0)
  • src/cli/command-startup-policy.test.ts (modified, +9/-0)
  • src/commands/agents.commands.list.ts (modified, +18/-9)

Code Example

$ time openclaw cron list --all --json >/dev/null   # 1.0s
$ time openclaw skills list --json >/dev/null       # 0.9s
$ time openclaw sessions --all-agents --json >/dev/null  # 1.0s
$ time openclaw agents list --json >/dev/null       # 8.6s   ← outlier
$ time openclaw channels list --json >/dev/null     # 0.9s   ← same shape, fast

---

{ commandPath: ['agents'],          policy: { loadPlugins: 'always' } },
{ commandPath: ['agents', 'list'],  route: { id: 'agents-list' } },        // ← inherits 'always'
{ commandPath: ['channels'],        policy: { loadPlugins: 'always' } },
{ commandPath: ['channels', 'list'], policy: { loadPlugins: 'never' },
  route: { id: 'channels-list' } },                                        // ← explicit 'never'

---

return loadPlugins === 'always' || (loadPlugins === 'text-only' && !params.jsonOutputMode);

---

{
     commandPath: ['agents', 'list'],
     policy: { loadPlugins: 'text-only' },
     route: { id: 'agents-list' },
   }

---

const providerStatus = opts.json ? null : await buildProviderStatusIndex(cfg);
   // …skip per-summary provider/route enrichment when providerStatus is null
RAW_BUFFERClick to expand / collapse

Problem

openclaw agents list --json consistently takes ~7-9s on a fast host and ~11s in a container on 2026.4.23, while peer commands like cron list --all --json, skills list --json, and sessions ... --json stay sub-second on the same install. Programmatic JSON consumers (dashboards, monitoring scripts, IDE plugins) feel this acutely because they poll.

The cost is the bundled-extension import waterfall at CLI bootstrap (~60+ extension index.js modules in dist/extensions/ get pulled in to back per-agent provider/binding info).

Repro (host CLI, openclaw 2026.4.23, paired)

$ time openclaw cron list --all --json >/dev/null   # 1.0s
$ time openclaw skills list --json >/dev/null       # 0.9s
$ time openclaw sessions --all-agents --json >/dev/null  # 1.0s
$ time openclaw agents list --json >/dev/null       # 8.6s   ← outlier
$ time openclaw channels list --json >/dev/null     # 0.9s   ← same shape, fast

The channels list comparison is the key: same surface (list a config-defined collection as JSON), much faster.

Diagnosis

The CLI startup decides whether to load plugins via per-command policy in src/cli/command-catalog.ts:

{ commandPath: ['agents'],          policy: { loadPlugins: 'always' } },
{ commandPath: ['agents', 'list'],  route: { id: 'agents-list' } },        // ← inherits 'always'
{ commandPath: ['channels'],        policy: { loadPlugins: 'always' } },
{ commandPath: ['channels', 'list'], policy: { loadPlugins: 'never' },
  route: { id: 'channels-list' } },                                        // ← explicit 'never'

agents list has a route override but no policy override, so resolveCliCommandPathPolicy (in src/cli/command-path-policy.ts) keeps the parent's loadPlugins: 'always' from the agents entry. channels list has the explicit override and stays fast.

The runtime already supports a text-only mode in shouldLoadPluginsForCommandPath (src/cli/command-startup-policy.ts):

return loadPlugins === 'always' || (loadPlugins === 'text-only' && !params.jsonOutputMode);

But agents list doesn't opt into it.

There's also an in-command path: agentsListCommand in src/commands/agents.commands.list.ts calls buildProviderStatusIndex(cfg) unconditionally, and that triggers on-demand plugin loads even when opts.json is true.

Empirical patch

I locally added policy: { loadPlugins: 'text-only' } to the ['agents', 'list'] catalog entry (file: dist/command-execution-startup-BgJnfxRd.js in the published bundle, mapped from src/cli/command-catalog.ts):

beforeafter catalog patch
agents list --json warm~9s~5s
agents list (text)~9sunchanged

Half the cost gone from a one-line change. The remaining ~5s is the on-demand load inside buildProviderStatusIndex.

Proposed fix

Two-line change:

  1. src/cli/command-catalog.ts — opt agents list into text-only:

    {
      commandPath: ['agents', 'list'],
      policy: { loadPlugins: 'text-only' },
      route: { id: 'agents-list' },
    }
  2. src/commands/agents.commands.list.ts — skip provider enrichment when emitting JSON (and/or behind --bindings only):

    const providerStatus = opts.json ? null : await buildProviderStatusIndex(cfg);
    // …skip per-summary provider/route enrichment when providerStatus is null

JSON consumers lose the providers and routes fields (which require provider plugins to compute). For consumers that need them, they can pass --bindings to opt back in. Human text output is unchanged.

Impact

  • Programmatic callers that poll agents list --json go from ~10s/call to sub-second
  • For agent-dash (the consumer that surfaced this), our /api/dashboard cold path drops from 27s → ~2s and we can drop a 5-min cache TTL workaround we shipped to dodge this
  • Human agents list is unaffected
  • No schema break for callers that consume id, name, model, bindings, isDefault, identity*, workspace, agentDir — all derived from cfg without plugin load

Related

#67040 (broader plugin-discovery cache work) and #71690 (gateway-side cold-start) cover the bigger architectural picture; this issue is a small, surgical fix for the agents list JSON path that ships independently of those.

extent analysis

TL;DR

The proposed fix involves modifying the command-catalog.ts to opt agents list into text-only and updating agents.commands.list.ts to skip provider enrichment when emitting JSON.

Guidance

  • Update src/cli/command-catalog.ts to include policy: { loadPlugins: 'text-only' } for the ['agents', 'list'] command to reduce plugin loading overhead.
  • Modify src/commands/agents.commands.list.ts to conditionally skip buildProviderStatusIndex when opts.json is true to avoid unnecessary plugin loads.
  • Consider adding a --bindings option to allow consumers to opt-in to provider enrichment when needed.
  • Verify the fix by measuring the execution time of openclaw agents list --json before and after applying the changes.

Example

// src/cli/command-catalog.ts
{
  commandPath: ['agents', 'list'],
  policy: { loadPlugins: 'text-only' },
  route: { id: 'agents-list' },
}

// src/commands/agents.commands.list.ts
const providerStatus = opts.json ? null : await buildProviderStatusIndex(cfg);

Notes

The proposed fix assumes that the text-only mode is sufficient for JSON consumers and that the --bindings option can be used to opt-in to provider enrichment when needed. Additional testing may be required to ensure that the fix does not introduce any regressions.

Recommendation

Apply the proposed two-line change to opt agents list into text-only and skip provider enrichment when emitting JSON, as it provides a significant performance improvement for programmatic callers without affecting human text output.

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

openclaw - ✅(Solved) Fix perf(cli): agents list --json shouldn't load all extension plugins [1 pull requests, 1 participants]