fix(#51): clone config defaults, null-unset route overrides, resettable session, telemetry on every command - #126
Merged
Conversation
…le session, telemetry on every command Four long-tail correctness defects from the #51 sweep: 1. `loadConfig` shallow-spread the frozen defaults, leaving the nested `telemetry` object aliased. One caller mutating its own config rewrote the defaults for the rest of the process — invisible in a one-shot CLI run, a cross-request leak in the MCP server and any library embedding. `cloneDefaults()` now deep-clones. 2. Route-policy override maps could add or change an entry but never remove one, so a global "always diff for Python" policy could not be opted out of per project. The maps are now `Record<string, EditRoute | null>` and `mergeOverrides` treats an explicit `null` as "unset". 3. The telemetry session id was a module-level `const`, so a long-lived host (MCP server, library embedding) reported every event under one session forever. Added `newSession()` and `setSessionId()`, both re-exported from the barrel. 4. Roughly half the CLI commands recorded no telemetry event at all, so health reports undercounted usage. `finish()` — the single choke point every command funnels through — now emits a fallback event when the action recorded none, gated on `getRecordedEventCount() === 0` so a richer per-command event is never double-counted. `telemetry *` and `uninstall` are exempt: recording there would grow the very log the command is inspecting, or outlive the directory it removes. Items 3, 4 and 5 of the issue were already fixed by #40 and #105 and have existing tests; item 3 gained a static guard against the `elapsed_ms: 0` regression. tests/longtail-51.test.ts adds 10 tests. Items 1 and 2 are exercised through a child process with an isolated HOME, because a real global config on the developer's machine masks the aliasing. Suite: 866 pass. No bench case: these are config and telemetry defects, outside the bench harness's edit-correctness model. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Owner
Author
|
🎉 This PR is included in version 4.4.15 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #51.
Four real defects from the long-tail sweep; three of the seven items in the issue (3, 4, 5) were already fixed by #40 and #105 and are covered by existing tests.
1.
loadConfigaliased the defaulttelemetryobjectA shallow spread of the frozen
DEFAULT_CONFIGleft the nestedtelemetryobject shared. One caller mutating its own config silently rewrote the defaults for the rest of the process. Invisible in the CLI (one process, one call); a cross-request leak in the MCP server and any library embedding.cloneDefaults()deep-clones.2. Route-policy overrides could not be unset
A higher-priority config could add or change an override but never remove one, so a global "always diff for Python" policy could not be opted out of per project. The override maps are now
Record<string, EditRoute | null>;mergeOverridestreats an explicitnullas "unset", andpolicyForcecoercesnulltoundefined.3. Telemetry session id was fixed for process lifetime
A module-level
constmeant a long-lived host reported every event under one session forever.newSession()andsetSessionId(id)added and re-exported from the barrel.4. Roughly half the CLI commands emitted no telemetry event
Health reports therefore undercounted usage. Rather than thread
recordEventthrough ~20 actions with severalfinish()sites each, the fallback event is emitted insidefinish()itself — the single choke point every command funnels through — gated ongetRecordedEventCount() === 0so richer per-command events are never double-counted.telemetry *anduninstallare exempt: recording there would grow the very log being inspected, or outlive the directory being removed. (CommanderpostActionhooks were considered and rejected: the CLI uses syncprogram.parse()with async actions.)Verification
tests/longtail-51.test.ts— 10 tests. Items 1 and 2 run as a child-process probe under an isolatedHOME, because a real global config on the dev machine rebuilds the nested object and masks the aliasing. Item 4 enumerates commands in child processes and asserts exactly one event each withelapsed_ms > 0, plus a test thattelemetry summarywrites nothing.src/core/config.ts→ 3 failures (items 1, 2); stashingsrc/core/exit-codes.ts→ 2 failures (item 4).bun run lint:docsclean; no scratch leftovers.🤖 Generated with Claude Code