Skip to content

enhancement: 3x faster CLI invocation, unify boolean/env parsing, streamline CLI startup paths#1195

Merged
steipete merged 4 commits into
openclaw:mainfrom
gumadeiras:main
Jan 18, 2026
Merged

enhancement: 3x faster CLI invocation, unify boolean/env parsing, streamline CLI startup paths#1195
steipete merged 4 commits into
openclaw:mainfrom
gumadeiras:main

Conversation

@gumadeiras

@gumadeiras gumadeiras commented Jan 18, 2026

Copy link
Copy Markdown
Member

I noticed the clawdbot invocation getting slower over the last couple of weeks, especially in weak hardware. Can add up quickly if repeatedly running commands

A few example benchmarks running on a cheap VPS 2 core / 2GB:

command before after speedup
clawdbot 4.9s±400ms 1.4s±300ms 3.5x faster
clawdbot health 5.7s±400ms 1.8s±200ms 3.1x faster
clawdbot status 6.1s±400ms 2.0s±400ms 3.0x faster
clawdbot memory status 6.2s±300ms 2.0s±400ms 3.1x faster

Summary

  • Introduced lightweight CLI routing and config guards
  • Tightened logging imports and lazy logger initialization
  • Expanded focused tests for argv, env, frontmatter, utils, and subclis
  • Standardized truthy env parsing and boolean handling across CLI/runtime

Per-file Changes

File Change Why
boolean.ts Added parseBooleanValue() Centralize string/boolean parsing and remove duplicated logic
boolean.test.ts Added coverage for default/custom truthy/falsy parsing Lock in allowed values and behavior
env.ts Added isTruthyEnvValue() Standardize env checks and avoid ad-hoc string comparisons
env.test.ts Added tests for env truthiness behavior Prevent regressions in env flag parsing
shell-env.ts Replaced inline parsing with isTruthyEnvValue(); added deferral flag helper Make env fallback logic consistent and easier to reason about
path-env.ts Switched bootstrap guard to isTruthyEnvValue() Consistent env-based bootstrap behavior
cli-timing.ts Added timing helper for structured CLI timing Emit structured timing data when enabled, without leaking into default paths
io.ts Added config caching controls and deferred shell env fallback Reduce repeated IO and allow explicit opt-out of caching/fallback
entry.ts Added CLAWDBOT_NO_RESPAWN handling; standardized env checks Avoid unwanted respawns and keep flag handling uniform
argv.ts Added argv helpers for command path parsing and help/version detection Simplify CLI routing and make entrypoints more declarative
argv.test.ts Added coverage for argv parsing/normalization Ensure consistent CLI argument interpretation
helpers.ts (CLI) Added resolveActionArgs() Normalize lazy subcommand arg parsing and reduce per-command boilerplate
register.subclis.ts Switched to lazy subcommand registration with explicit argv parsing Faster startup and more reliable dispatch for subcommands
register.subclis.test.ts Added tests for lazy subcommand registration Verify dispatch behavior and startup paths
build-program.ts Threaded argv into subcommand registration Deterministic parsing based on actual argv
context.ts Moved channel options resolution behind a helper Avoid eager plugin loading during context setup
channel-options.ts Added explicit resolver for CLI channel options; gated eager plugin loading behind env Control when plugins are loaded and make opt-in behavior explicit
plugin-registry.ts Extracted plugin registry priming into a helper Centralize when/how plugin registry is primed
config-guard.ts Centralized config validation/migration checks Keep CLI commands consistent and safer around config readiness
preaction.ts Removed config migration from pre-action hook Ensure migrations only run where explicitly intended via config guard
register.agent.ts Added config guard to agent/agents commands Enforce consistent config readiness before running agents
register.status-health-sessions.ts Added config guard to status/health/session commands Keep config checks consistent across status endpoints
helpers.ts (message actions) Added config guard to message actions Prevent running with invalid or unmigrated config
route.ts Added fast-path routing for read-only commands Reduce CLI startup overhead on common read-only paths
memory-cli.ts Extracted runMemoryStatus() and added config guard Reuse status logic and enforce consistent validation
memory-cli.test.ts Mocked config guard Keep tests isolated from real config state
browser-cli-state.ts Reused parseBooleanValue() for on/off parsing Remove duplicate boolean parsing logic
utils.ts Reused parseBooleanValue() for query/body boolean parsing Ensure consistent HTTP/CLI boolean handling
utils.test.ts Added tests for toBoolean() Lock in allowed query/body flag values
frontmatter.ts (flags) Reused parseBooleanValue() for frontmatter flags Standardize frontmatter boolean handling
frontmatter.test.ts (skills) Added coverage for skill invocation policy parsing Ensure skill policy flags parse consistently
frontmatter.ts (hooks) Reused parseBooleanValue() for hook enablement Same standardized parsing for hooks
frontmatter.test.ts (hooks) Added tests for hook invocation policy parsing Lock in hook policy semantics
server.ts Switched env skip flag to isTruthyEnvValue() Accept standard truthy strings across server flags
doctor-update.ts Standardized update-in-progress check via isTruthyEnvValue() Consistent env semantics around in-progress updates
health.ts Standardized debug flag checks via isTruthyEnvValue() Uniform debug toggles
server-startup.ts Standardized Gmail/channel skip flags via isTruthyEnvValue() One consistent way to skip integrations
server-reload-handlers.ts Standardized reload skip flags via isTruthyEnvValue() Predictable reload behavior
server-browser.ts Standardized skip flag via isTruthyEnvValue() Same env semantics in browser server path
bonjour.ts Standardized disable flag via isTruthyEnvValue() Consistent discovery disable toggle
cli-runner.ts Standardized log-output toggle via isTruthyEnvValue() Uniform log output flag parsing
pi-embedded-subscribe.raw-stream.ts Standardized raw-stream toggle via isTruthyEnvValue() Consistent raw stream enablement
accounts.ts Standardized debug toggle via isTruthyEnvValue() Same debug semantics for accounts
batch-gemini.ts Standardized debug toggle via isTruthyEnvValue() Same debug semantics for Gemini batch
embeddings-gemini.ts Standardized debug toggle via isTruthyEnvValue() Same debug semantics for Gemini embeddings
console.ts Made logger initialization lazy Avoid early logger dependency issues and circular import problems
logging.ts Explicitly re-exported subsystem helpers Stabilize logging imports across the codebase
globals.ts Avoided barrel import for getLogger() Prevent brittle barrel resolution and runtime failures
constants.ts Switched to direct subsystem logger import Same: avoid barrel indirection issues
cli-credentials.ts Switched to direct subsystem logger import More robust logging wiring in CLI
anthropic.setup-token.live.test.ts Standardized live-test gating via isTruthyEnvValue() Consistent gating for live tests
google-gemini-switch.live.test.ts Same as above
minimax.live.test.ts Same as above
models.profiles.live.test.ts Same as above
pi-embedded-runner-extraparams.live.test.ts Same as above
zai.live.test.ts Same as above
pw-session.browserless.live.test.ts Same as above
gateway-cli-backend.live.test.ts Same as above
gateway-models.profiles.live.test.ts Same as above
audio.live.test.ts Same as above

Overall Improvements

  • Consistent env flag parsing across CLI, gateway, memory, and live-test toggles via parseBooleanValue() and isTruthyEnvValue()
  • Clear separation of config validation through a shared config-guard instead of implicit pre-action hooks
  • Faster CLI paths for common read-only commands by routing early and lazily registering subcommands
  • More robust logging setup through direct subsystem imports and lazy console logger initialization

Reasoning

  • Centralizing boolean/env parsing removes duplication and inconsistent truthy handling, and makes supported values explicit and test-backed.
  • The config guard provides predictable config validation/migration behavior per command, without global side effects from pre-action hooks.
  • Lazy subcommand registration and route-first paths reduce CLI startup time, especially for read-only and status-style commands.
  • Logging import changes eliminate runtime failures and circularities caused by barrel exports.

Tests

  • pnpm vitest run boolean.test.ts env.test.ts frontmatter.test.ts frontmatter.test.ts utils.test.ts argv.test.ts register.subclis.test.ts embeddings.test.ts globals.test.ts gmail-setup-utils.test.ts memory-cli.test.ts
  • pnpm vitest run argv.test.ts register.subclis.test.ts
  • pnpm lint && pnpm build && pnpm test
> oxlint --type-aware src test

Found 0 warnings and 0 errors.
Finished in 1.2s on 2236 files with 104 rules using 12 threads.

 Test Files  774 passed | 1 skipped (775)
      Tests  4036 passed | 7 skipped (4043)
   Start at  16:05:35
   Duration  141.35s (transform 11.32s, setup 212.33s, import 158.99s, tests 119.97s, environment 45ms)
  • I've been running this version and testing as many commands as possible and just talking to multiple agents normally and running tasks

AI Assisted: GPT-5.2 Codex

To hopefully give some intuition and to make sure I validated routes correctly:

CLI entrypoints:

  • Binary entry: entry.ts
  • Main CLI runner: run-main.ts runCli().

Routing diagrams (current):

  1. Top-level invocation
clawdbot
  -> [src/entry.ts]
      - profile/env prep + respawn guard
      -> import [src/cli/run-main.ts]
          -> runCli()
  1. Fast-path routing (NEW, faster for read-only commands)
runCli()
  -> tryRouteCli() in [src/cli/route.ts]
      - health/status/sessions/agents list/memory status
      - runs command directly, returns early
  -> (if routed) exit

Why faster: avoids building Commander program + importing all subcommands.

  1. Full CLI path (unchanged behavior, but LAZY subcommands now)
runCli()
  -> buildProgram() in [src/cli/program/build-program.ts]
      -> registerSubCliCommands() in [src/cli/program/register.subclis.ts]
          - placeholders only
          - real subcommand imported on demand
  -> program.parseAsync(...)

Why faster: only loads the chosen subcommand instead of importing all.

What’s new/different/faster:

  • NEW: Fast-path routing for read-only commands in route.ts.
    Faster because it bypasses Commander setup and lazy imports entirely.

  • NEW: Lazy subcommand registration in register.subclis.ts.
    Faster because only the invoked subcommand’s module tree is loaded.

  • NEW: Deferred plugin registry loading via channel-options.ts and plugin-registry.ts.
    Faster because plugin discovery is avoided unless explicitly needed.

  • NEW: Config caching in io.ts.
    Faster because repeated config reads in short-lived runs reuse cached content.

Summary flow (combined):

clawdbot
  -> [src/entry.ts]
  -> [src/cli/run-main.ts]
      -> tryRouteCli() [NEW fast path]
      -> else buildProgram() -> lazy subcommands [NEW lazy load]

Copilot AI review requested due to automatic review settings January 18, 2026 21:28

Copilot AI left a comment

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.

Pull request overview

This PR significantly improves CLI startup performance (3x faster on low-end hardware) through lazy loading, optimized routing, and centralized parsing. The changes also standardize environment variable and boolean parsing across the entire codebase.

Changes:

  • Introduced centralized boolean/env parsing utilities (parseBooleanValue(), isTruthyEnvValue()) replacing dozens of ad-hoc string comparisons
  • Implemented lazy subcommand registration and fast-path routing for common read-only commands (health, status, sessions, memory status)
  • Added config caching (200ms default TTL) and deferred shell environment loading to reduce repeated I/O
  • Extracted config validation/migration logic into a dedicated config-guard module used explicitly by commands instead of global pre-action hooks
  • Made logger initialization lazy to avoid circular import issues and early initialization overhead

Reviewed changes

Copilot reviewed 58 out of 58 changed files in this pull request and generated no comments.

Show a summary per file
File Description
boolean.ts, boolean.test.ts New centralized boolean parsing utility with comprehensive test coverage
env.ts, env.test.ts Added isTruthyEnvValue() helper for consistent env flag checks
cli-timing.ts New structured CLI timing instrumentation for performance monitoring
argv.ts, argv.test.ts New argv parsing helpers for command path extraction and flag detection
route.ts Fast-path routing for read-only commands bypassing full program initialization
register.subclis.ts, register.subclis.test.ts Major refactor to lazy subcommand registration with explicit argv parsing
config-guard.ts Centralized config validation/migration extracted from pre-action hooks
preaction.ts Removed config migration logic (moved to config-guard)
context.ts Deferred plugin loading to avoid eager initialization
plugin-registry.ts, channel-options.ts Extracted helpers for explicit control over plugin/channel loading
register.agent.ts, register.status-health-sessions.ts, message/helpers.ts Added explicit config guards to commands
memory-cli.ts, memory-cli.test.ts Extracted runMemoryStatus() for reuse in routing
io.ts Added 200ms config caching and deferred shell env fallback support
shell-env.ts, path-env.ts Standardized env checks using isTruthyEnvValue()
entry.ts Added CLAWDBOT_NO_RESPAWN support and standardized env checks
console.ts Made logger initialization lazy to avoid circular dependencies
globals.ts, various CLI files Switched to direct subsystem logger imports avoiding barrel resolution
50+ files across gateway, agents, memory, browser, hooks Replaced ad-hoc env/boolean string comparisons with isTruthyEnvValue() and parseBooleanValue()
frontmatter.ts (hooks & skills), browser-cli-state.ts, routes/utils.ts Reused parseBooleanValue() for consistent boolean handling
All live test files Standardized test gating using isTruthyEnvValue()

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@steipete steipete self-assigned this Jan 18, 2026
@steipete

Copy link
Copy Markdown
Contributor

Gustavo, ohh love this - yeah perf was bothering me too, just didn't expect it'd be as simple as this! Thanks!

gumadeiras and others added 4 commits January 18, 2026 23:10
Add shared parseBooleanValue()/isTruthyEnvValue() and apply across CLI, gateway, memory, and live-test flags for consistent env handling.
Introduce route-first fast paths, lazy subcommand registration, and deferred plugin loading to reduce CLI startup overhead.
Centralize config validation via ensureConfigReady() and add config caching/deferred shell env fallback for fewer IO passes.
Harden logger initialization/imports and add focused tests for argv, boolean parsing, frontmatter, and CLI subcommands.
Deleted the unused import of hasHelpOrVersion from argv.js to clean up the code.
Leftover functions I was using the benchmark and time CLI calls
@steipete steipete merged commit 5f975a4 into openclaw:main Jan 18, 2026
@steipete

Copy link
Copy Markdown
Contributor

Landed via temp rebase onto main.

  • Gate: pnpm lint && pnpm build && pnpm test
  • Land commit: d5c8172
  • Merge commit: 5f975a4

Thanks @gumadeiras!

lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
enhancement: 3x faster CLI invocation, unify boolean/env parsing, streamline CLI startup paths
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
enhancement: 3x faster CLI invocation, unify boolean/env parsing, streamline CLI startup paths
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants