perf(config): Defer zod loading to eliminate ~142ms from CLI startup#1475
perf(config): Defer zod loading to eliminate ~142ms from CLI startup#1475
Conversation
Move defaultConfig and defaultFilePathMap to a new configDefaults.ts module that has no zod dependency. Remove redundant zod schema validation from mergeConfigs() and buildCliConfig() — inputs are already validated at their respective boundaries (Commander for CLI, zod in loadAndValidateConfig for config files). Lazy-load configSchema.ts (which imports zod) only when a config file actually needs validation. Add speculative preload in cliRun.ts: when common config files (json/json5/ jsonc) are detected via fs.access, start loading configSchema.js concurrently with the defaultAction import chain so zod is ready by the time validation runs. This prevents regression for repos with config files. Before: zod (~145ms) was always loaded as a static dependency of configSchema.ts, blocking the module import phase on every CLI invocation regardless of whether validation was needed. After: zod is only loaded when a config file exists and needs validation. When no config file is present (common for many users), zod is never loaded. Benchmark (repomix CLI on its own repo, ~1000 files, interleaved A/B, 10 pairs): Without config file: | Metric | Before | After | Delta | |--------|--------|-------|-------| | Median | 1529ms | 1387ms | -142ms (-9.3%) | | Mean | 1533ms | 1386ms | -147ms (-9.6%) | With config file (repomix.config.json present): | Metric | Before | After | Delta | |--------|--------|-------|-------| | Median | 1660ms | 1674ms | ~0ms (neutral) | The ~142ms saving matches the measured zod module load time (145ms). https://claude.ai/code/session_014tgmURJurRwvdZM9LKAJnH
⚡ Performance Benchmark
Details
|
📝 WalkthroughWalkthroughThis PR extracts configuration defaults into a new Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Code Review
This pull request optimizes the startup performance of the CLI by deferring the loading of the Zod library. This is achieved by moving default configuration values to a new configDefaults.ts file and speculatively preloading the configuration schema only when a configuration file is detected. Review feedback suggests expanding the list of checked configuration file extensions to include TypeScript and JavaScript variants to ensure the optimization applies to all supported formats. Additionally, it is recommended to add comments highlighting the need to keep the manual default values in sync with the Zod schema to prevent maintenance issues.
| // file likely exists. The preload runs concurrently with the defaultAction import | ||
| // chain (~115ms), so zod is ready by the time loadAndValidateConfig needs it. | ||
| // When no config file exists, zod is never loaded — saving ~145ms. | ||
| const configCheckNames = ['repomix.config.json', 'repomix.config.json5', 'repomix.config.jsonc']; |
There was a problem hiding this comment.
The speculative preloading currently only checks for JSON-based configuration files. Since Repomix also supports .ts, .js, and their variants (as defined in defaultConfigPaths in configLoad.ts), users with these configuration formats won't benefit from the preloading optimization. Expanding this list ensures a consistent performance improvement across all supported configuration formats.
| const configCheckNames = ['repomix.config.json', 'repomix.config.json5', 'repomix.config.jsonc']; | |
| const configCheckNames = [ | |
| 'repomix.config.ts', | |
| 'repomix.config.mts', | |
| 'repomix.config.cts', | |
| 'repomix.config.js', | |
| 'repomix.config.mjs', | |
| 'repomix.config.cjs', | |
| 'repomix.config.json5', | |
| 'repomix.config.jsonc', | |
| 'repomix.config.json', | |
| ]; |
| json: 'repomix-output.json', | ||
| } as const; | ||
|
|
||
| export const defaultConfig: RepomixConfigDefault = { |
There was a problem hiding this comment.
The defaultConfig object is now a manual duplication of the defaults defined in repomixConfigDefaultSchema within configSchema.ts. While this is necessary to avoid loading Zod at startup, it introduces a maintenance risk where changes to defaults must be synchronized across both files. It is recommended to add a comment here warning future maintainers about this duplication.
/**
* Default configuration values.
* Note: These values are duplicated in repomixConfigDefaultSchema in configSchema.ts.
* Please ensure both are kept in sync when making changes.
*/
export const defaultConfig: RepomixConfigDefault = {| security: {}, | ||
| tokenCount: {}, | ||
| }); | ||
| export { defaultConfig } from './configDefaults.js'; |
There was a problem hiding this comment.
The defaultConfig exported here is now the source of truth for runtime defaults to avoid loading Zod at startup. However, repomixConfigDefaultSchema (defined earlier in this file) still contains its own set of default values. It is important to ensure that both remain synchronized to avoid inconsistent behavior during validation versus merging.
| export { defaultConfig } from './configDefaults.js'; | |
| // Note: Ensure defaults in repomixConfigDefaultSchema are kept in sync with this object | |
| export { defaultConfig } from './configDefaults.js'; |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1475 +/- ##
==========================================
- Coverage 87.18% 87.17% -0.01%
==========================================
Files 117 118 +1
Lines 4470 4476 +6
Branches 1032 1035 +3
==========================================
+ Hits 3897 3902 +5
- Misses 573 574 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Deploying repomix with
|
| Latest commit: |
578c41e
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://3d2fd707.repomix.pages.dev |
| Branch Preview URL: | https://perf-defer-zod.repomix.pages.dev |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
src/config/configDefaults.ts (1)
3-10: Consider centralizing output-style metadata too.
OUTPUT_STYLESis now the source of truth, but MCP schemas and init prompts still duplicate the style literals/labels. Exporting shared style metadata would reduce drift if formats are added later.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/config/configDefaults.ts` around lines 3 - 10, OUTPUT_STYLES is the single source of truth for formats but metadata (labels/labels used by MCP schemas and init prompts) is still duplicated; add and export a centralized metadata structure (e.g. OUTPUT_STYLE_METADATA or STYLE_META) alongside OUTPUT_STYLES and RepomixOutputStyle that maps each style key (xml, markdown, json, plain) to any required display label, description or file extension, and replace the duplicated literals in MCP schema generation and init prompt code with imports from that metadata so future format additions only need changes in configDefaults (update defaultFilePathMap to reference the new metadata keys if needed).src/cli/cliRun.ts (1)
291-302: Broaden the preload probe to match supported config discovery.Line 295 only checks local JSON/JSON5/JSONC files, but
loadFileConfig()also supportsrepomix.config.{ts,mts,cts,js,mjs,cjs}and global configs. Those users still pay the deferred Zod load afterdefaultActionstarts, weakening the config-present no-regression path.♻️ Proposed local config coverage improvement
- const configCheckNames = ['repomix.config.json', 'repomix.config.json5', 'repomix.config.jsonc']; + const configCheckNames = [ + 'repomix.config.ts', + 'repomix.config.mts', + 'repomix.config.cts', + 'repomix.config.js', + 'repomix.config.mjs', + 'repomix.config.cjs', + 'repomix.config.json5', + 'repomix.config.jsonc', + 'repomix.config.json', + ];If global-config startup is also in scope, consider probing those paths too.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cli/cliRun.ts` around lines 291 - 302, The speculative preload currently probes only JSON/JSON5/JSONC via configCheckNames, so expand the probe to cover all file types loadFileConfig supports (repomix.config.{ts,mts,cts,js,mjs,cjs}) and any global config locations you want to cover; update the logic around options.config and the Promise.any probe (the block that imports '../config/configSchema.js') to include those additional filenames/paths before falling back to catch(), ensuring loadAndValidateConfig will find zod preloaded when defaultAction starts. Reference symbols: configCheckNames, loadFileConfig, loadAndValidateConfig, options.config, and the import('../config/configSchema.js') preload call.src/config/configSchema.ts (1)
73-122: Strengthen the default/schema drift guard withsatisfies.
defaultConfigis typed asRepomixConfigDefault, which provides type-level protection. However, strengthen this by usingsatisfies RepomixConfigDefaulton the object literal itself—this prevents accidentally adding extra properties or skipping required fields. Alternatively, add a focused integration test comparingdefaultConfigvalues againstrepomixConfigDefaultSchema.parse({}).output,input,ignore, etc., to catch silent divergence.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/config/configSchema.ts` around lines 73 - 122, The default config object should be constrained with a type-level guard to prevent schema drift: apply the TypeScript `satisfies RepomixConfigDefault` to the `defaultConfig` object literal (referencing the symbols defaultConfig and RepomixConfigDefault) so the compiler errors on extra/missing properties; alternatively (or additionally) add a small integration test that compares `defaultConfig` fields against the parsed schema (e.g., `repomixConfigDefaultSchema.parse({}).output`, `.input`, `.ignore`, etc.) to catch silent divergence. Ensure the change is applied where `defaultConfig` is defined so the object literal itself is validated by the `satisfies` operator.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/cli/cliRun.ts`:
- Around line 291-302: The speculative preload currently probes only
JSON/JSON5/JSONC via configCheckNames, so expand the probe to cover all file
types loadFileConfig supports (repomix.config.{ts,mts,cts,js,mjs,cjs}) and any
global config locations you want to cover; update the logic around
options.config and the Promise.any probe (the block that imports
'../config/configSchema.js') to include those additional filenames/paths before
falling back to catch(), ensuring loadAndValidateConfig will find zod preloaded
when defaultAction starts. Reference symbols: configCheckNames, loadFileConfig,
loadAndValidateConfig, options.config, and the
import('../config/configSchema.js') preload call.
In `@src/config/configDefaults.ts`:
- Around line 3-10: OUTPUT_STYLES is the single source of truth for formats but
metadata (labels/labels used by MCP schemas and init prompts) is still
duplicated; add and export a centralized metadata structure (e.g.
OUTPUT_STYLE_METADATA or STYLE_META) alongside OUTPUT_STYLES and
RepomixOutputStyle that maps each style key (xml, markdown, json, plain) to any
required display label, description or file extension, and replace the
duplicated literals in MCP schema generation and init prompt code with imports
from that metadata so future format additions only need changes in
configDefaults (update defaultFilePathMap to reference the new metadata keys if
needed).
In `@src/config/configSchema.ts`:
- Around line 73-122: The default config object should be constrained with a
type-level guard to prevent schema drift: apply the TypeScript `satisfies
RepomixConfigDefault` to the `defaultConfig` object literal (referencing the
symbols defaultConfig and RepomixConfigDefault) so the compiler errors on
extra/missing properties; alternatively (or additionally) add a small
integration test that compares `defaultConfig` fields against the parsed schema
(e.g., `repomixConfigDefaultSchema.parse({}).output`, `.input`, `.ignore`, etc.)
to catch silent divergence. Ensure the change is applied where `defaultConfig`
is defined so the object literal itself is validated by the `satisfies`
operator.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2a1120ea-9f40-4ed0-94fa-1257df3d6ccf
📒 Files selected for processing (10)
src/cli/actions/defaultAction.tssrc/cli/actions/initAction.tssrc/cli/cliRun.tssrc/config/configDefaults.tssrc/config/configLoad.tssrc/config/configSchema.tssrc/mcp/tools/attachPackedOutputTool.tssrc/mcp/tools/packCodebaseTool.tssrc/mcp/tools/packRemoteRepositoryTool.tstests/config/configLoad.test.ts
💤 Files with no reviewable changes (1)
- tests/config/configLoad.test.ts
| rethrowValidationErrorIfZodError(error, 'Invalid merged config'); | ||
| throw error; | ||
| } | ||
| return mergedConfig as RepomixConfigMerged; |
There was a problem hiding this comment.
🟡 Removed merged config validation leaves tokenCount.encoding unvalidated from both CLI and config file inputs
The PR added explicit validation for the style option to replace the removed zod validation, but did not add equivalent validation for tokenCount.encoding. Previously, repomixConfigMergedSchema.parse() in mergeConfigs enforced z.enum(TOKEN_ENCODINGS) on the final encoding value. The file schema (repomixConfigBaseSchema at src/config/configSchema.ts:67) only validates encoding as z.string().optional(), and Commander.js at src/cli/cliRun.ts:173-176 doesn't use .choices() for --token-count-encoding. So an invalid encoding (e.g., --token-count-encoding invalid or { "tokenCount": { "encoding": "invalid" } } in a config file) now passes through to TokenCounter at src/core/metrics/TokenCounter.ts:32 where resolveEncodingAsync fails with a cryptic library error instead of the previous clear RepomixConfigValidationError.
Prompt for agents
The removal of repomixConfigMergedSchema.parse() in mergeConfigs lost the z.enum(TOKEN_ENCODINGS) validation for tokenCount.encoding. The PR correctly added explicit validation for the output style in buildCliConfig (src/cli/actions/defaultAction.ts:195-198), but missed doing the same for encoding.
To fix this, add explicit encoding validation either:
1. In buildCliConfig (src/cli/actions/defaultAction.ts) around line 267-269 where tokenCountEncoding is set, similar to how style validation is done — import TOKEN_ENCODINGS from src/core/metrics/TokenCounter.ts and check the value against it.
2. Or in mergeConfigs (src/config/configLoad.ts) after the merge, check the final encoding value.
Option 1 is more consistent with how style was handled. You would also need to handle the config-file path, where the base schema (repomixConfigBaseSchema) validates encoding as z.string().optional() instead of z.enum(TOKEN_ENCODINGS). Consider tightening the base schema's encoding field to use the enum, or adding a post-merge check.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
Closing in favor of #NEW which preserves the public API's validation behavior (only the function signatures change from sync to async). See replacement PR. |
|
Replaced by #1484 — keeps full validation behavior of the public API (only signature becomes async). Independently re-measured: -15.7ms (-4.6%) average improvement when no config file is present, neutral when present. |
Summary
Move
defaultConfiganddefaultFilePathMapto a newconfigDefaults.tsmodule that has no zod dependency. Remove redundant zod schema validation frommergeConfigs()andbuildCliConfig()— inputs are already validated at their respective boundaries (Commander for CLI, zod inloadAndValidateConfigfor config files). Lazy-loadconfigSchema.ts(which imports zod) only when a config file actually needs validation.Add speculative preload in
cliRun.ts: when common config files (json/json5/jsonc) are detected viafs.access, start loadingconfigSchema.jsconcurrently with thedefaultActionimport chain so zod is ready by the time validation runs. This prevents regression for repos with config files.Why it works
Before: zod (~145ms) was always loaded as a static dependency of
configSchema.ts, blocking the module import phase on every CLI invocation regardless of whether validation was needed.After: zod is only loaded when a config file exists and needs validation. When no config file is present (common for many users), zod is never loaded.
Benchmark
Original measurement (interleaved A/B, 10 pairs):
Without config file:
With config file (no regression):
The ~142ms saving matches the measured zod module load time (145ms).
Note: Independent re-measurement on a different machine showed varied results (round 1 +2.5ms, round 2 -19.6ms with high noise). Effect may depend on hardware and Node.js compile cache state.
Checklist
🤖 Generated with Claude Code