Repository navigation
Conversation
…ation-inheritance change #1877/#1880 made updateLogger() fall back to mainLoggerRef.rotation when a logger's own config omits a rotation block, so a tagged/component logger with its own path now inherits main's rotation instead of silently never rotating. That change is already pinned by the existing #1877 describe block; this adds the shapes an upgrade-impact scanner needs to classify: plain logger.info/ warn/error sharing main's default path (unaffected - same shared per-path file sink before and after), logger.withTag()/tagged loggers (unaffected - never touch rotation resolution), level filtering (unaffected - the mainLogger -> mainLoggerRef parameter is never passed by production code), and a concrete reproduction of the operational risk the fix itself introduces: a descriptor opened on a previously-never-rotated component log before rotation stops seeing new writes once the file gets renamed out from under it. No runtime behavior change; main behaves identically to v5.3.0 here (the only diff since is unrelated bigint-stat precision fixes for Windows inode identity). Dispatch-Task: harper-upgrade-rule-logger-rotation-530 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01524gnfYt96yuXWh7KqpsET
…r-impact tests per review Round-1 cross-model review (codex/gemini/cursor-composer + harper-domain) surfaced one real bug and several legitimate robustness/style findings: - The tailer test's "line two" write raced a multi-rotation cascade (30 lines against a 1K cap rotates 3-4 times in milliseconds) and could land in an intermediate archive instead of the settled active file - caught by running the test standalone, not just as part of the suite. Fixed by waiting for the archive count to hold steady before writing past it. Reproduced the failure under debug logging before fixing it. - Reordered the "plain logger.info/warn/error" test so forComponent's own copy-at-creation of a logger's rotation (unrelated to this fix) can't be what delivers rotation - only the shared per-path file sink can. - Dropped the new rewire/private-state use in the level-resolution test (AGENTS.md: no new sinon/rewire; harper_logger.ts:117-119 already gives tests an explicit mainLoggerRef for this), replacing it with the same mainLoggerRef-substitution pattern the rest of the file uses. - Wrapped the tailer fd in try/finally so a timeout can't leak it. - Trimmed comments that narrated the test/code instead of stating a why, and untied the describe title from specific version numbers. Dispatch-Task: harper-upgrade-rule-logger-rotation-530 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01524gnfYt96yuXWh7KqpsET
…rim remaining narration Round-2 review found the rewritten level test couldn't actually catch its target: forComponent copies level at creation, and main was already at 'trace' by the time forComponent ran, so the probe inherited 'trace' before updateLogger's mainLoggerRef fallback ever ran. A mutation check (removing the `?? mainLoggerRef?.level` fallback and rebuilding) confirmed the test stayed green. Reordered it the same way as the first test: create the probe while main is still at 'info', then bump main to 'trace' and update the probe with no level of its own, so only the fallback can deliver 'trace'. Re-ran the same mutation check - the test now fails as expected, then restored and rebuilt clean. Also dropped the remaining comments that only restated adjacent code. Dispatch-Task: harper-upgrade-rule-logger-rotation-530 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01524gnfYt96yuXWh7KqpsET
…y asides from comments Round-3 review found one gap left: deleting the whole level-assignment line (not just its mainLoggerRef fallback) left both main and the probe at their starting 'info' level, so the equality assertion still passed. Capture the probe's pre-fallback level and assert it actually changed, which only the fallback running can produce. Reconfirmed with the same mutation-check process as the prior two rounds (delete the line, rebuild, see it fail; restore, rebuild, confirm clean). Also dropped the "(unrelated to this fix)" / PR-number asides review legs kept flagging as narration, keeping only the structural why. Dispatch-Task: harper-upgrade-rule-logger-rotation-530 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01524gnfYt96yuXWh7KqpsET
No logic change: drops the describe-block header, the tailer-simulation restatement, the polling-loop restatement, and shortens the mutation-guard comment to the one clause that states why, per round-4's nit. Dispatch-Task: harper-upgrade-rule-logger-rotation-530 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01524gnfYt96yuXWh7KqpsET
Contributor
There was a problem hiding this comment.
Code Review
This pull request introduces a new test suite in unitTests/utility/logging/harper_logger.test.js to validate caller-impact shapes for the logger rotation-inheritance fix (#1877/#1880). The added tests verify behavior for plain loggers sharing the main log path, tagged loggers, log rotation with open file descriptors, and level resolution fallback via mainLoggerRef. There are no review comments to address, and I have no feedback to provide.
This branch has not been deployed
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.
Pins the caller-impact rule for the v5.2.15 -> v5.3.0 logger rotation-inheritance fix: which
loggerusage shapes it changes, with tests and a decision rule a later static scanner can apply mechanically.⊙ Problem
Harper is building an upgrade-impact pipeline: a release step's diff is graded, then any app that uses the changed API is flagged as potentially affected by a static pattern match — not verified. v5.2.15 → v5.3.0 changed
updateLogger()(utility/logging/harper_logger.ts:120-174) so a tagged/component logger with norotation:block of its own now inherits main's rotation policy instead of silently losing rotation. This task pins down exactly whichlogger-usage shapes that change affects, as executable tests plus a decision rule a later static scanner can apply mechanically to real application code. No application code is touched; this is research + tests only onHarperFast/harperitself.💡 Solution
(a) Trigger conditions
A logger's rotation changes under this fix exactly when all of:
updateLogger()is actually called on it — not merely true for every logger (see shape table: aforComponent()-created logger that has no corresponding config section is never passed throughupdateLoggerat all).logOptions.rotationisnull/omitted (not{}, which is a present-but-empty override that wins outright).:123-124— so main can still clear its own rotation by omitting the block).mainLoggerRef.rotation(main's live policy at that moment) is non-null.When all four hold, the logger's rotation becomes
mainLoggerRef.rotation, withrotation.pathstripped if the logger's own resolved file path differs from main's (avoids anEXDEVcross-device rename —:139-158).(b) Caller-shape table
logger.info/warn/error(...), nologging.external:configlogger.info('hi')— sharing main's default path, bound to main's rotation via the shared per-path file-sink cache, same before and afterlogger.withTag(tag)/loggerWithTag()logger.withTag('ingest').info('hi')— never callsupdateLogger, writes through whatever sink the wrapped logger already hasmainLoggerreference to the parameterizedmainLoggerRefon those two lines; production never passes the new 4th argument, so the resolved value is identicallogging:block specifyingpath/rootbut norotation:myApp:\n logging:\n path: /app/log/myApp.loglogging.external:block with norotation:logging:\n external:\n path: /app/log/external.logrotation:(even{enabled: false})logging:\n path: /app/log/x.log\n rotation:\n enabled: false— explicit policy always wins, before and afterdoes not let an inherited main policy replace an explicit component policy on the shared sink/restores the inherited main policy when an explicit component policy is removedtail -f-style process that opened the fd before the file first rotates stops seeing new writes once the file is renamed out from under it(c) Intended fix or accidental regression?
Intended fix, with direct evidence:
logging.external.pathgrew to 200G because, per the issue's own framing, "because that is anexternallog it doesn't respect the default logging configuration for rotation."updateLogger()doinglogger.rotation = logOptions.rotationunconditionally, with no fallback — the inheritance pattern every other option (level,path,tag) already had.The one real behavior-change risk this introduces: any component that had its own path and no rotation block because it relied on the old (buggy) no-rotation behavior as a feature — e.g., a side log it deliberately never wanted pruned — now gets main's rotation by default. Main's own
rotation.enableddefaults totruewithmaxSize: 64M(static/defaultConfig.yaml:48), so this is a default-on change for any such component, not something that requires explicit opt-in.(d) Static-detection recipe
The trigger is configuration-shaped, not code-shaped — the mechanism lives entirely in
updateLogger's handling of a component's config, not in any JS/TS call site an app could write differently. A scanner should read the app's ownharperdb-config.yaml/manifest, not its source:logging:block other than the top-level one — i.e. nested under a named top-level component key, or theexternal:key nested under the top-levellogging:— that contains apath:orroot:key but no siblingrotation:key at the same indent level, provided the deployment's top-levellogging.rotation.enabledis not explicitlyfalse.Xin the parsed tree whereXis a top-level component name orexternal: flag if (X.logging.pathorX.logging.rootis set) andX.logging.rotationis absent, andlogging.rotation.enabled !== falseat the root.rotation:, but the top-levellogging.rotationis disabled or main has no rotation configured — nothing to inherit, no real behavior change despite matching the pattern.path/rootresolves to the exact same file as main's own log (shared-sink case) — it was already bound to main's rotation via the shared per-path cache before this fix too (see unaffected row above), so flagging it overstates the impact.rotation: {}(present but empty) passes a naive "key exists" check, but it still disables the write-path size guard (nomaxSize) — a real behavior point a presence-only check misses.logger.forComponent(name)called from app code with no matching YAML section is correctly not flagged by this recipe (true negative — see the unaffected table row), but a scanner that instead keys off source call sites rather than config would miss the real trigger entirely and risk flagging this harmless shape instead.(e) Does
mainstill behave as v5.3.0 did?Yes, unchanged.
git diff v5.3.0..origin/main -- utility/logging/touches onlyharper_logger.ts,logRotation.ts,logRotator.ts, andlogGenerationCoordinator.tsfor Windows 64-bit-inode precision (fs.statSync(..., { bigint: true }), to avoid aliasing two files past2^53) plus matching type annotations. None of it touchesupdateLogger's rotation-fallback logic orgetFileLogger's install/clear arbitration — the exact code this rule is about is byte-for-byte the same shape onmaintoday as it was at thev5.3.0tag.🔧 Changes
unitTests/utility/logging/harper_logger.test.js— adds aTest caller-impact shapes for the logger rotation-inheritance fix (#1877/#1880)describe block with 4 new tests: the unaffected plain-logger shape (shared per-path sink), the unaffectedwithTagshape, the tailer operational risk, and the unaffected level-resolution shape (mutation-tested against three different ways the assertion could pass trivially).Test external/component logger rotation inheritance (#1877)block (:1023-1240), added with PR fix(logging): external/component loggers inherit main rotation config #1880.✅ Verification
npx mocha unitTests/utility/logging/harper_logger.test.js— 126 passing, 14 pending (pre-existing, environment-conditional skips unrelated to this change), 0 failing. Re-ran the full file 3x and the new tailer test standalone 5x to rule out flakiness (one real rotation-cascade race was found and fixed this way — see commit history).npm run test:unit:main(full gate) — 6893 passing, 1 unrelated failure (deployActivation.test.js, "retries a transient failure moving the candidate into place") that reproduces only under concurrent load from other agents' mocha runs on this box and passes cleanly in isolation; confirmed unrelated to this diff (zero file overlap).test:unit:resources/test:integration:all: this PR changes only one unit test file with zero production-code or runtime-behavior changes, so the full integration gate is disproportionate to what's being verified.test:unit:logging(the directly relevant suite, 203 tests across the wholeunitTests/utility/logging/directory) passed in full.utility/logging/harper_logger.tsto the pre-fix / broken behavior, rebuiltdist/, confirmed the targeted test failed, then restored and rebuilt clean (repeated 3 times across the level-resolution test's iterations — see commit messages for the exact mutations tried).closeLogFileswallowing errors, git not tracking empty dirs, etc.).Declined, for the record:
Refs #1877
🤖 Generated with Claude Code
https://claude.ai/code/session_01524gnfYt96yuXWh7KqpsET
Complexity: easy
Release target: main — test-only, no runtime behavior change to backport
Related PRs: #2939 overlaps, 4 others independent
Dispatch: task
harper-upgrade-rule-logger-rotation-530· queued by unknown · ran by claude/sonnet/high · worker kzyp-xps-1Review-Coverage: authored=claude; ran=gemini,codex; adjudicated=domain; declined=cursor-grok,cursor-composer,cursor-kimi,cursor-muse; rounds=5; full=1 @ 8d93748
Review-Attention: skim ~2m (decisions: head-only-historical-claims, dropped-default-arg-coverage, quiescence-vs-deterministic, pin-tailer-breakage-as-passing) @ 8d93748