Summary
router.stats.filePath is declared as an override for the stats file path, but the parser drops it, and the collector would not honor the full filename even if it received the field.
Why it matters
A user or test can reasonably set router.stats.filePath: /tmp/custom-name.jsonl and expect stats to be written to that exact file. Today the field does not survive parsing. Separately, TokenStatsCollector treats filePath as a directory hint by taking dirname(filePath) and then always writing stats.jsonl. This can make stats data appear missing, cause tests to read the wrong file, or make multiple custom configs collide on the same default filename.
Evidence
src/router/config/schema.ts:58 documents filePath as an override for the default ~/.pilotdeck/router/stats.json path.
src/router/config/schema.ts:59 declares filePath?: string.
src/router/config/parseRouterConfig.ts:491 enters parseStats.
src/router/config/parseRouterConfig.ts:508 parses enabled.
src/router/config/parseRouterConfig.ts:510 parses modelPricing.
src/router/config/parseRouterConfig.ts:532 parses baselineModel.
src/router/config/parseRouterConfig.ts:538 returns { enabled, modelPricing, baselineModel }, dropping filePath.
src/router/stats/TokenStatsCollector.ts:71 checks config?.filePath.
src/router/stats/TokenStatsCollector.ts:72 uses path.dirname(config.filePath).
src/router/stats/TokenStatsCollector.ts:76 always sets jsonlPath to path.join(routerDir, "stats.jsonl").
Validation
Validation level: dynamic parser reproduction plus source-control-flow confirmation.
Minimal reproduction:
- Parse router config containing:
router.stats.enabled: true
router.stats.filePath: /tmp/custom-name.jsonl
- Inspect parsed router stats.
- Inspect collector path construction for the same field.
Key output:
- Parser output is
{ enabled: true }.
filePath is absent from parsed stats.
- Collector code would convert
/tmp/custom-name.jsonl to directory /tmp, then write /tmp/stats.jsonl.
Boundary: the parser loss was reproduced directly. The collector behavior is confirmed by source control flow. This does not validate concurrent stats writes or migration behavior.
Expected behavior
The behavior should match the field contract. Either:
router.stats.filePath means a full output file path, in which case the parser should preserve it and the collector should write exactly that path; or
- the supported override is only a directory, in which case the schema/config field should be renamed or documented as a directory setting instead of
filePath.
Existing coverage checked
No matching fix was found.
Checked adjacent work:
Search terms checked included router.stats.filePath, TokenStatsCollector filePath, and stats.jsonl custom.
Suggested fix
Pick one contract and make both parser and collector follow it.
Recommended contract:
- Treat
router.stats.filePath as a full file path.
- Preserve
filePath in parseStats.
- In
TokenStatsCollector, create path.dirname(filePath) but set jsonlPath to filePath itself.
- Keep the current default path when
filePath is absent.
If the intended contract is directory-only, rename the setting or update schema/docs so users are not promised a file path override.
Suggested tests
- Parsing
router.stats.filePath preserves the exact string in RouterStatsConfig.
TokenStatsCollector({ enabled: true, filePath: "/tmp/custom-name.jsonl" }) writes to /tmp/custom-name.jsonl, not /tmp/stats.jsonl.
- Default behavior without
filePath still writes the existing default stats file.
- Migration logic still works when a custom full file path is configured.
Submitted with Codex.
Summary
router.stats.filePathis declared as an override for the stats file path, but the parser drops it, and the collector would not honor the full filename even if it received the field.Why it matters
A user or test can reasonably set
router.stats.filePath: /tmp/custom-name.jsonland expect stats to be written to that exact file. Today the field does not survive parsing. Separately,TokenStatsCollectortreatsfilePathas a directory hint by takingdirname(filePath)and then always writingstats.jsonl. This can make stats data appear missing, cause tests to read the wrong file, or make multiple custom configs collide on the same default filename.Evidence
src/router/config/schema.ts:58documentsfilePathas an override for the default~/.pilotdeck/router/stats.jsonpath.src/router/config/schema.ts:59declaresfilePath?: string.src/router/config/parseRouterConfig.ts:491entersparseStats.src/router/config/parseRouterConfig.ts:508parsesenabled.src/router/config/parseRouterConfig.ts:510parsesmodelPricing.src/router/config/parseRouterConfig.ts:532parsesbaselineModel.src/router/config/parseRouterConfig.ts:538returns{ enabled, modelPricing, baselineModel }, droppingfilePath.src/router/stats/TokenStatsCollector.ts:71checksconfig?.filePath.src/router/stats/TokenStatsCollector.ts:72usespath.dirname(config.filePath).src/router/stats/TokenStatsCollector.ts:76always setsjsonlPathtopath.join(routerDir, "stats.jsonl").Validation
Validation level: dynamic parser reproduction plus source-control-flow confirmation.
Minimal reproduction:
router.stats.enabled: truerouter.stats.filePath: /tmp/custom-name.jsonlKey output:
{ enabled: true }.filePathis absent from parsed stats./tmp/custom-name.jsonlto directory/tmp, then write/tmp/stats.jsonl.Boundary: the parser loss was reproduced directly. The collector behavior is confirmed by source control flow. This does not validate concurrent stats writes or migration behavior.
Expected behavior
The behavior should match the field contract. Either:
router.stats.filePathmeans a full output file path, in which case the parser should preserve it and the collector should write exactly that path; orfilePath.Existing coverage checked
No matching fix was found.
Checked adjacent work:
router.stats.filePathin the parser or make the collector honor the full file path.Search terms checked included
router.stats.filePath,TokenStatsCollector filePath, andstats.jsonl custom.Suggested fix
Pick one contract and make both parser and collector follow it.
Recommended contract:
router.stats.filePathas a full file path.filePathinparseStats.TokenStatsCollector, createpath.dirname(filePath)but setjsonlPathtofilePathitself.filePathis absent.If the intended contract is directory-only, rename the setting or update schema/docs so users are not promised a file path override.
Suggested tests
router.stats.filePathpreserves the exact string inRouterStatsConfig.TokenStatsCollector({ enabled: true, filePath: "/tmp/custom-name.jsonl" })writes to/tmp/custom-name.jsonl, not/tmp/stats.jsonl.filePathstill writes the existing default stats file.Submitted with Codex.