Skip to content

fix(server): a raising tuning provider no longer takes down get_merged() for everyone (#899) - #904

Merged
byrongamatos merged 1 commit into
mainfrom
fix/899-logger
Jul 11, 2026
Merged

byrongamatos merged 1 commit into
mainfrom
fix/899-logger

Conversation

@byrongamatos

@byrongamatos byrongamatos commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Closes #899. One word.

     except Exception:
-        logger.exception("tuning provider %r raised during get_merged()", provider_id)
+        log.exception("tuning provider %r raised during get_merged()", provider_id)

There is no logger in server.py — the module logger is log.

So the handler written to swallow-and-report a bad provider instead raised NameError from inside the except, and that NameError propagated straight out of get_merged().

The effect was the exact opposite of what the handler is for:

  • one misbehaving plugin took the whole merged-tunings call down for every other provider
  • the traceback named the wrong problem (name 'logger' is not defined, not the provider that actually blew up)
  • and nothing was ever logged either — the logging call was the thing that crashed

Why it survived

Found by pyflakes while carving server.py (R3b). It lasted because nothing exercised the failure path — no test ever had a provider raise.

That's the whole reason this class of bug is invisible: it lives only on error paths, so the suite stays green and the feature is broken exactly when it matters.

The test

tests/test_tuning_provider_isolation.py is that path:

  • a raising provider must not lose the healthy providers' tunings, nor the defaults
  • and the failure must actually be logged — swallowing is only acceptable if it reports

Bite-tested: restoring logger fails both.

The log assertion attaches caplog's handler to the feedBack logger directly — it sets propagate=False, so pytest's root capture sees nothing from it. test_plugins.py has a capture_logger() for this, but it isn't importable here (pyproject pins pythonpath = [".", "lib"], so tests isn't a package). Three lines beat churning 21 call sites in an unrelated file to convert that helper into a fixture.

pytest 2410 · pyflakes: 0 undefined names in server.py · Codex 0.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved resilience when a tuning provider fails, allowing other tuning data and defaults to remain available.
    • Provider failures are now recorded in logs instead of triggering an additional error.
  • Tests

    • Added coverage verifying provider isolation and failure logging.

…d() for everyone (#899)

One word. server.py's TuningProviderRegistry.get_merged():

    except Exception:
-       logger.exception("tuning provider %r raised during get_merged()", provider_id)
+       log.exception("tuning provider %r raised during get_merged()", provider_id)

There is no `logger` in server.py — the module logger is `log`. So the handler written to
swallow-and-report a bad provider instead raised NameError from inside the except, and that
NameError propagated out of get_merged().

The effect was the exact OPPOSITE of what the handler is for: one misbehaving plugin took
the whole merged-tunings call down for every other provider, AND the traceback named the
wrong problem ("name 'logger' is not defined" rather than the provider that actually blew
up). Doubly silent: nothing was ever logged either, because the logging call was the thing
that crashed.

Found by pyflakes while carving server.py (R3b). It survived because NOTHING exercised the
failure path — no test ever had a provider raise. That is the whole reason this class of
bug is invisible: it lives only on error paths, so the suite is green and the feature is
broken exactly when it matters.

tests/test_tuning_provider_isolation.py is that path:
  * a raising provider must not lose the HEALTHY providers' tunings, nor the defaults
  * and the failure must actually be LOGGED — swallowing is only acceptable if it reports

Bite-tested: restoring `logger` fails both.

(The log assertion attaches caplog's handler to the feedBack logger directly. It sets
propagate=False, so pytest's root capture sees nothing from it — test_plugins.py has a
capture_logger() for this, but it is not importable here: pyproject pins pythonpath to
[".", "lib"], so `tests` is not a package. Three lines beat churning 21 call sites in an
unrelated file to convert that helper into a fixture.)

pytest 2410, pyflakes 0 undefined names in server.py, Codex 0.

Closes #899

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Tuning provider error isolation

Layer / File(s) Summary
Provider error handling
server.py
get_merged() now uses the module’s log logger when a tuning provider raises.
Provider isolation regression tests
tests/test_tuning_provider_isolation.py
Adds isolated registry setup and verifies failing providers do not prevent healthy tunings or defaults from being returned, while failures are logged.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main fix: handling a failing tuning provider in get_merged() without breaking other providers.
Linked Issues check ✅ Passed The change replaces the undefined logger with log and adds tests proving failures are logged and healthy tunings still merge.
Out of Scope Changes check ✅ Passed The edits stay focused on the logger fix and regression tests for TuningProviderRegistry.get_merged().
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/899-logger

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
tests/test_tuning_provider_isolation.py (1)

1-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep test docstrings minimal.

These docstrings include issue history and implementation details that can become stale. Replace them with concise purpose statements.

As per coding guidelines: *.py: Use type hints sparingly, such as Path | None, dict, and list; keep docstrings minimal.

Also applies to: 30-30, 51-52

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_tuning_provider_isolation.py` around lines 1 - 12, Shorten the
test docstrings in tests/test_tuning_provider_isolation.py, including the
locations noted at lines 30–30 and 51–52, to concise statements describing each
test’s purpose. Remove issue history, implementation details, and stale
explanatory context while preserving the tests’ behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_tuning_provider_isolation.py`:
- Around line 21-26: Update the registry fixture’s module-isolation teardown:
after importing and yielding the registry, restore the prior
sys.modules["server"] entry or remove the fixture-imported module via
monkeypatch so later tests do not retain the temporary CONFIG_DIR
initialization. Keep the existing TuningProviderRegistry setup unchanged.
- Around line 62-68: Restore the original level of the feedBack logger after the
registry.get_merged() assertion completes. Capture the logger’s level before
setting it to ERROR, then restore that saved level in the existing finally block
alongside removing caplog.handler.
- Around line 42-47: Update the default-preservation assertion in the tuning
provider isolation test to check for a specific known default tuning value,
rather than only asserting that merged["guitar"] is non-empty. Keep the existing
healthy-provider assertion unchanged and use the repository’s established
default tuning name.

---

Nitpick comments:
In `@tests/test_tuning_provider_isolation.py`:
- Around line 1-12: Shorten the test docstrings in
tests/test_tuning_provider_isolation.py, including the locations noted at lines
30–30 and 51–52, to concise statements describing each test’s purpose. Remove
issue history, implementation details, and stale explanatory context while
preserving the tests’ behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a3be0194-b694-4adc-987a-1cc21f7ee8e1

📥 Commits

Reviewing files that changed from the base of the PR and between 70dbe45 and ecd2d2a.

📒 Files selected for processing (2)
  • server.py
  • tests/test_tuning_provider_isolation.py

Comment on lines +21 to +26
@pytest.fixture()
def registry(monkeypatch, tmp_path):
monkeypatch.setenv("CONFIG_DIR", str(tmp_path))
sys.modules.pop("server", None)
mod = importlib.import_module("server")
yield mod.TuningProviderRegistry()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Restore the cached server module after the fixture.

Removing server from sys.modules and leaving the newly imported module cached can contaminate later tests: they may see a module initialized with the temporary CONFIG_DIR. Use monkeypatch.delitem(sys.modules, "server", raising=False) or explicitly restore the previous module during teardown.

Proposed fix
-    sys.modules.pop("server", None)
+    monkeypatch.delitem(sys.modules, "server", raising=False)
     mod = importlib.import_module("server")
     yield mod.TuningProviderRegistry()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@pytest.fixture()
def registry(monkeypatch, tmp_path):
monkeypatch.setenv("CONFIG_DIR", str(tmp_path))
sys.modules.pop("server", None)
mod = importlib.import_module("server")
yield mod.TuningProviderRegistry()
`@pytest.fixture`()
def registry(monkeypatch, tmp_path):
monkeypatch.setenv("CONFIG_DIR", str(tmp_path))
monkeypatch.delitem(sys.modules, "server", raising=False)
mod = importlib.import_module("server")
yield mod.TuningProviderRegistry()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_tuning_provider_isolation.py` around lines 21 - 26, Update the
registry fixture’s module-isolation teardown: after importing and yielding the
registry, restore the prior sys.modules["server"] entry or remove the
fixture-imported module via monkeypatch so later tests do not retain the
temporary CONFIG_DIR initialization. Keep the existing TuningProviderRegistry
setup unchanged.

Comment on lines +42 to +47
assert "My Tuning" in merged["guitar"], (
"the healthy provider's tuning is missing — one raising provider took down the "
"merged result for everyone"
)
# and the default tunings survive
assert merged["guitar"], "default tunings were lost"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that a known default tuning survives.

assert merged["guitar"] is vacuous because the healthy provider already adds "My Tuning". The test would pass even if all defaults were lost, so it does not cover the stated default-preservation objective.

Proposed fix
+    default_guitar_names = set(registry.get_merged()["guitar"])
     registry.register("bad-plugin", boom)
     registry.register("good-plugin", good)

     merged = registry.get_merged()
...
-    # and the default tunings survive
-    assert merged["guitar"], "default tunings were lost"
+    assert default_guitar_names <= merged["guitar"].keys(), (
+        "default tunings were lost"
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert "My Tuning" in merged["guitar"], (
"the healthy provider's tuning is missing — one raising provider took down the "
"merged result for everyone"
)
# and the default tunings survive
assert merged["guitar"], "default tunings were lost"
default_guitar_names = set(registry.get_merged()["guitar"])
registry.register("bad-plugin", boom)
registry.register("good-plugin", good)
merged = registry.get_merged()
assert "My Tuning" in merged["guitar"], (
"the healthy provider's tuning is missing — one raising provider took down the "
"merged result for everyone"
)
assert default_guitar_names <= merged["guitar"].keys(), (
"default tunings were lost"
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_tuning_provider_isolation.py` around lines 42 - 47, Update the
default-preservation assertion in the tuning provider isolation test to check
for a specific known default tuning value, rather than only asserting that
merged["guitar"] is non-empty. Keep the existing healthy-provider assertion
unchanged and use the repository’s established default tuning name.

Comment on lines +62 to +68
lg = logging.getLogger("feedBack")
lg.addHandler(caplog.handler)
lg.setLevel(logging.ERROR)
try:
registry.get_merged()
finally:
lg.removeHandler(caplog.handler)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Restore the feedBack logger level.

The test sets a process-global logger level to ERROR and never restores it, which can make later tests order-dependent.

Proposed fix
+    previous_level = lg.level
     lg.addHandler(caplog.handler)
     lg.setLevel(logging.ERROR)
     try:
         registry.get_merged()
     finally:
         lg.removeHandler(caplog.handler)
+        lg.setLevel(previous_level)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
lg = logging.getLogger("feedBack")
lg.addHandler(caplog.handler)
lg.setLevel(logging.ERROR)
try:
registry.get_merged()
finally:
lg.removeHandler(caplog.handler)
previous_level = lg.level
lg.addHandler(caplog.handler)
lg.setLevel(logging.ERROR)
try:
registry.get_merged()
finally:
lg.removeHandler(caplog.handler)
lg.setLevel(previous_level)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_tuning_provider_isolation.py` around lines 62 - 68, Restore the
original level of the feedBack logger after the registry.get_merged() assertion
completes. Capture the logger’s level before setting it to ERROR, then restore
that saved level in the existing finally block alongside removing
caplog.handler.

@byrongamatos
byrongamatos merged commit 6b8f79d into main Jul 11, 2026
5 checks passed
@byrongamatos
byrongamatos deleted the fix/899-logger branch July 11, 2026 23:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

server.py: logger is undefined in TuningProviderRegistry.get_merged()'s except handler (NameError instead of a log line)

1 participant