Stop cascading logging errors in the Windows desktop app - #15197
Stop cascading logging errors in the Windows desktop app#15197rtibbles wants to merge 2 commits into
Conversation
🟡 Waiting for changesLast updated: 2026-09-09 16:09 UTC |
Build Artifacts
Smoke test screenshot |
rtibblesbot
left a comment
There was a problem hiding this comment.
PR #15197 — the four-part diagnosis (contended file → handler error → re-logged through the captured stderr → unbounded queue) holds up against the code, and each fix lands where the cycle closes.
One blocking issue: bounding the queue makes QueueListener.stop() raise queue.Full in exactly the saturated state this PR expects, breaking server shutdown. Rest are suggestions/nitpicks inline.
Verified locally: pytest kolibri/utils/tests (526 passed), pytest platforms/desktop-app/tests/test_streams.py (5 passed), prek run --from-ref upstream/develop --to-ref HEAD clean. CI: no failures, one check still running. No UI files changed, so visual verification does not apply.
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Ran a phased review pipeline over the pull request diff:
- Classified the diff to select review passes (core, frontend, backend) and whether manual QA was required
- Core review pass checked correctness, design, architecture, testing, completeness, and DRY/SRP/Rule-of-Three principles
- Specialized frontend/backend review passes applied framework-specific lenses where those files changed
- For UI changes: manual QA and an accessibility audit against a live dev server, when available
- Checked CI status and linked issue acceptance criteria
- Synthesized one review from those passes and chose the verdict from the findings, CI status, and QA evidence
| record._logger_name = self.logger_name | ||
| return record | ||
|
|
||
| def enqueue(self, record: logging.LogRecord) -> None: |
There was a problem hiding this comment.
✅ Resolved — addressed in the current code.
suggestion: Dropped records leave no trace. Together with log.raiseExceptions = False in the desktop app, a log with a hole in it is indistinguishable from a quiet one — when the next report of this bug arrives, nothing in kolibri.txt will say records were lost.
The listener thread is outside the reentrant path, so a counter incremented here and reported from there (on drain, or at cleanup_queue_logging) would not reopen the cycle.
There was a problem hiding this comment.
Declined both counter suggestions because there's no obvious place to log this information that doesn't get us into the same reentrant logging loop.
There was a problem hiding this comment.
This seems like a reasonable suggestion that doesn't seem impossible. Instead of immediately logging this, it could simply track the quantity of dropped logs, and at some point in the future when the queue is freed, enqueue a message that N logs were dropped.
dfb4c61 to
76d7c0f
Compare
rtibblesbot
left a comment
There was a problem hiding this comment.
PR #15197 — 5 of 8 prior findings resolved; 3 open, none new. CI passing.
- suggestion logger.py:66 — silent drops.
- suggestion logger.py:187 — rollover failure undiscoverable.
- nitpick logger.py:212 — no DST correction.
Prior-finding status
RESOLVED — kolibri/utils/logger.py:470 — stop() raises Full
RESOLVED — kolibri/utils/logger.py:17 — KOLIBRI_LOG_QUEUE_MAX_SIZE=0
RESOLVED — platforms/desktop-app/src/kolibri_app/logger.py:20 — raiseExceptions global
RESOLVED — kolibri/utils/tests/test_handler.py:120 — kwarg assertion
UNADDRESSED — kolibri/utils/logger.py:66 — silent drops
UNADDRESSED — kolibri/utils/logger.py:187 — rollover undiscoverable
UNADDRESSED — kolibri/utils/logger.py:212 — DST correction
ACKNOWLEDGED — platforms/desktop-app/tests/test_streams.py:50 — bounded writer
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Ran an automatic code-only delta review triggered by new commits on a previously reviewed PR:
- Retrieved prior bot reviews via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Core review pass only — specialized frontend/backend lenses and manual QA run when a review is explicitly requested
- Synthesized one review from the passes and chose the verdict from the findings, CI status, and QA evidence
bjester
left a comment
There was a problem hiding this comment.
The suggestion about having some visibility into dropped logs does seem reasonable, but it should certainly take a different approach than immediately logging that. With AI being used here, that does not seem like a tall ask.
| record._logger_name = self.logger_name | ||
| return record | ||
|
|
||
| def enqueue(self, record: logging.LogRecord) -> None: |
There was a problem hiding this comment.
This seems like a reasonable suggestion that doesn't seem impossible. Instead of immediately logging this, it could simply track the quantity of dropped logs, and at some point in the future when the queue is freed, enqueue a message that N logs were dropped.
Windows will not rename a log file a second process still holds open. The failed rollover left rolloverAt in the past, so every subsequent record retried the rename, and each retry's own error report fed back in as more records. Postpone the next attempt instead, and bound the log queue so a listener that cannot drain stops growing it, counting what that costs and reporting the total once the queue has room again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KXc8oMzGTawizmmAuxeJfg
The UI process and the server subprocess shared kolibri-app.txt, so at midnight one of them could never rotate it. Give the server its own file, and stop logging's handler-error reports from re-entering logging through the stdout/stderr redirect the app installs, both when they recurse in-thread and when the queue carries them across threads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KXc8oMzGTawizmmAuxeJfg
76d7c0f to
7e01fa4
Compare
rtibblesbot
left a comment
There was a problem hiding this comment.
PR #15197 — 6 of 8 prior findings resolved; 2 acknowledged (declined by the author), none still open. No new findings. CI still pending at the time of review.
Prior-finding status
RESOLVED — kolibri/utils/logger.py:496 — listener.stop() raises queue.Full on a saturated queue
RESOLVED — kolibri/utils/logger.py:67 — dropped records leave no trace
RESOLVED — kolibri/utils/logger.py — KOLIBRI_LOG_QUEUE_MAX_SIZE=0 silently restores the unbounded queue
RESOLVED — platforms/desktop-app/src/kolibri_app/logger.py:20 — logging.raiseExceptions is module state
RESOLVED — kolibri/utils/tests/test_handler.py — bounded-queue test asserted only the maxsize= kwarg
RESOLVED — platforms/desktop-app/tests/test_streams.py:50 — praise, bounded recursive writer
ACKNOWLEDGED — kolibri/utils/logger.py:213 — the except OSError guard is wider and narrower than the failure it documents
ACKNOWLEDGED — kolibri/utils/logger.py:238 — _postpone_rollover omits the DST correction
@rtibblesbot's comments are generated by an LLM, and should be evaluated accordingly
How was this generated?
Ran an automatic code-only delta review triggered by new commits on a previously reviewed PR:
- Retrieved prior bot reviews via the GitHub API
- Classified each prior finding as RESOLVED, UNADDRESSED, ACKNOWLEDGED, or CONTESTED
- Only raised NEW findings for newly introduced code
- Core review pass only — specialized frontend/backend lenses and manual QA run when a review is explicitly requested
- Synthesized one review from the passes and chose the verdict from the findings, CI status, and QA evidence
Summary
Fixed by:
References
Fixes #15150
Reviewer guidance
kolibri-app-server.txtinC:\ProgramData\kolibri\logskolibri-app.txtalongside itNoting that the thing that seemed to set off the error in @pcenov's case was an error from a scheduled task - so running a background task might be sufficient to trigger this.
I have not tested on Windows.
AI usage
Used Claude Code to write the fix and its tests, then to review the resulting diff — that pass caught the Windows smoke test still looking for the renamed log file, and two guards that only closed the in-thread half of the loop. Verified with the
kolibri/utilsand desktop-app test suites, prek, and a local review pass over the diff.