Skip to content

fix(handlers): prevent daemon crash on non-serializable SWIG objects (B59)#163

Merged
BANANASJIM merged 2 commits intomasterfrom
fix/b59-descriptors-crash
Mar 1, 2026
Merged

fix(handlers): prevent daemon crash on non-serializable SWIG objects (B59)#163
BANANASJIM merged 2 commits intomasterfrom
fix/b59-descriptors-crash

Conversation

@BANANASJIM
Copy link
Owner

@BANANASJIM BANANASJIM commented Mar 1, 2026

User description

Summary

  • _enum_name() now applies str() to non-primitive objects without .name, preventing raw SWIG objects from reaching JSON serialization
  • Daemon's json.dumps() now catches TypeError and returns a JSON-RPC -32603 error instead of crashing the process
  • Fixes cascade e2e failure where cat /draws/<eid>/descriptors killed the daemon, causing all subsequent tests to get "no active session"

Test plan

  • Unit: _enum_name SWIG fallback returns str
  • Unit: daemon serialization resilience (TypeError → error response)
  • E2E: 147/147 passed (was 128/147 before fix)
  • pixi run lint && pixi run test — 2449 passed, 93.81% coverage

PR Type

Bug fix


Description

  • Prevent daemon crash when handlers return non-serializable SWIG objects

  • _enum_name() now converts non-primitive objects to strings safely

  • run_server() catches TypeError during JSON serialization and returns JSON-RPC error

  • Fixes cascade e2e test failures from daemon process termination


Diagram Walkthrough

flowchart LR
  A["Handler returns<br/>non-serializable object"] -->|"_enum_name()<br/>converts to str"| B["Safe object<br/>representation"]
  A -->|"json.dumps()<br/>TypeError"| C["TypeError caught<br/>in run_server"]
  C -->|"Returns JSON-RPC<br/>error -32603"| D["Client receives<br/>error response"]
  B -->|"json.dumps()<br/>succeeds"| E["Normal response<br/>sent to client"]
Loading

File Walkthrough

Relevant files
Error handling
daemon_server.py
Add TypeError guard to json.dumps serialization                   

src/rdc/daemon_server.py

  • Wrapped json.dumps(response) in try-except to catch TypeError
  • On serialization error, constructs JSON-RPC error response with code
    -32603
  • Clears binary_path when serialization fails to prevent file
    transmission
  • Logs warning message for debugging serialization failures
+12/-1   
Bug fix
_helpers.py
Convert non-serializable objects to strings safely             

src/rdc/handlers/_helpers.py

  • Enhanced _enum_name() to handle non-primitive objects without .name
    attribute
  • Explicitly passes through primitive types (str, int, float) and None
    unchanged
  • Converts non-serializable objects to string representation using str()
  • Prevents raw SWIG objects from reaching JSON serialization
+6/-2     
Tests
test_daemon_output_quality.py
Add tests for SWIG object string conversion                           

tests/unit/test_daemon_output_quality.py

  • Added test_swig_fallback_returns_str() to verify SWIG objects convert
    to strings
  • Added test_float_passthrough() to ensure float primitives pass through
    unchanged
  • Tests cover the B59 fix for non-serializable object handling
+13/-0   
test_daemon_server_unit.py
Add serialization resilience tests for daemon                       

tests/unit/test_daemon_server_unit.py

  • Added TestSerializationResilience class with two test methods
  • test_non_serializable_result_returns_error() verifies handler can
    return unserializable objects
  • test_json_dumps_guard_produces_valid_error() validates the error
    response structure
  • Tests confirm JSON-RPC error code -32603 and proper error message
    formatting
+62/-0   

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Improved JSON serialization error handling to gracefully manage non-serializable responses, returning appropriate error messages instead of server failures.
    • Enhanced object serialization robustness to handle edge cases more effectively.
  • Tests

    • Added comprehensive test coverage for serialization resilience and error handling scenarios.

…(B59)

_enum_name now returns str() for non-primitive objects without .name,
and run_server catches TypeError on json.dumps to return a JSON-RPC
error instead of crashing the daemon process.
@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

Warning

Rate limit exceeded

@BANANASJIM has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 12 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 6651dbc and 7c2ad5d.

📒 Files selected for processing (3)
  • src/rdc/daemon_server.py
  • src/rdc/handlers/_helpers.py
  • tests/unit/test_daemon_server_unit.py
📝 Walkthrough

Walkthrough

The changes add robust serialization error handling for JSON-RPC responses. When json.dumps() fails to serialize a response, an error payload with code -32603 is constructed and sent instead. Additionally, the _enum_name helper is updated to handle non-serializable objects by converting them to strings, with comprehensive test coverage for both modifications.

Changes

Cohort / File(s) Summary
JSON-RPC Serialization Error Handling
src/rdc/daemon_server.py
Added try-catch around json.dumps(response) to detect TypeError; constructs an error response with code -32603 and serialization error message, clears binary payload, and logs warning if serialization fails.
Enum Name Processing
src/rdc/handlers/_helpers.py
Updated _enum_name to return .name attribute if present, pass through primitives (str, int, float) and None unchanged, and convert other non-serializable objects to strings.
Serialization Resilience Tests
tests/unit/test_daemon_server_unit.py
Adds TestSerializationResilience class with two tests: verifies non-serializable results trigger error responses and validates correct JSON-RPC error payload structure with code -32603.
Enum Name Tests
tests/unit/test_daemon_output_quality.py
Adds two test cases to TestEnumName: validates Swig fallback string conversion and float value passthrough behavior.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Suggested labels

Review effort 2/5

Poem

A bunny codes with careful thought,
Serialization bugs are caught,
Enums dance in pristine light,
JSON errors set things right! 🐰✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically identifies the main change: preventing daemon crashes caused by non-serializable SWIG objects, which is the primary focus of the changeset across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/b59-descriptors-crash

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 1, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Information disclosure

Description: The JSON-RPC error response echoes the raw exception text (f"serialization error: {exc}")
back to the client, which can disclose internal type names/object representations and
other implementation details that may aid debugging or targeted probing; consider
returning a generic message to clients and keeping full details only in logs.
daemon_server.py [437-446]

Referred Code
    payload = json.dumps(response) + "\n"
except TypeError as exc:
    _log.warning("serialization error: %s", exc)
    err_resp: dict[str, Any] = {
        "jsonrpc": "2.0",
        "id": response.get("id"),
        "error": {"code": -32603, "message": f"serialization error: {exc}"},
    }
    payload = json.dumps(err_resp) + "\n"
    binary_path = None
Information disclosure

Description: Falling back to str(v) for arbitrary objects can inadvertently serialize and expose
sensitive information contained in an object's str/repr output (e.g., file paths,
tokens, addresses) to JSON consumers; consider a more constrained/redacted representation
for unknown types. _helpers.py [61-67]

Referred Code
def _enum_name(v: Any) -> Any:
    """Return .name for enum-like values, str() for non-serializable others."""
    if hasattr(v, "name"):
        return v.name
    if isinstance(v, (str, int, float)) or v is None:
        return v
    return str(v)
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Exception leaked to client: The JSON-RPC error message embeds the raw TypeError exception text ({exc}), potentially
exposing internal implementation details to clients instead of a generic user-facing
message.

Referred Code
err_resp: dict[str, Any] = {
    "jsonrpc": "2.0",
    "id": response.get("id"),
    "error": {"code": -32603, "message": f"serialization error: {exc}"},
}
payload = json.dumps(err_resp) + "\n"

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Limited audit context: The new serialization failure path logs only the exception message and does not include
request/method/user context needed to audit what action triggered the failure.

Referred Code
_log.warning("serialization error: %s", exc)
err_resp: dict[str, Any] = {
    "jsonrpc": "2.0",
    "id": response.get("id"),
    "error": {"code": -32603, "message": f"serialization error: {exc}"},
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Potential sensitive log data: Logging the raw serialization exception string may inadvertently include sensitive values
from the response object depending on the serializer/exception content.

Referred Code
_log.warning("serialization error: %s", exc)
err_resp: dict[str, Any] = {

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Stringification of objects: Converting arbitrary handler-returned objects via str(v) may surface unintended data in
responses unless upstream ensures sensitive data cannot be present in these objects.

Referred Code
def _enum_name(v: Any) -> Any:
    """Return .name for enum-like values, str() for non-serializable others."""
    if hasattr(v, "name"):
        return v.name
    if isinstance(v, (str, int, float)) or v is None:
        return v
    return str(v)

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

qodo-code-review bot commented Mar 1, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Reuse helper for error response

Refactor the TypeError exception handler to use the _error_response helper
function for creating the JSON-RPC error response, promoting code reuse and
consistency.

src/rdc/daemon_server.py [436-446]

 try:
     payload = json.dumps(response) + "\n"
 except TypeError as exc:
     _log.warning("serialization error: %s", exc)
-    err_resp: dict[str, Any] = {
-        "jsonrpc": "2.0",
-        "id": response.get("id"),
-        "error": {"code": -32603, "message": f"serialization error: {exc}"},
-    }
+    err_resp = _error_response(
+        response.get("id"), -32603, f"serialization error: {exc}"
+    )
     payload = json.dumps(err_resp) + "\n"
     binary_path = None
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies an opportunity to use the _error_response helper function, which improves code reuse and maintainability by centralizing error response creation.

Low
Possible issue
Ensure error response always serializable

Add a nested try...except block around the serialization of the error response
(err_resp) to handle the edge case where the error response itself is not
serializable.

src/rdc/daemon_server.py [445-446]

-payload = json.dumps(err_resp) + "\n"
+try:
+    payload = json.dumps(err_resp) + "\n"
+except TypeError:
+    # Fallback to a minimal error if err_resp still fails serialization
+    minimal = {
+        "jsonrpc": "2.0",
+        "id": None,
+        "error": {"code": -32603, "message": "serialization error: response malformed"}
+    }
+    payload = json.dumps(minimal) + "\n"
 binary_path = None
  • Apply / Chat
Suggestion importance[1-10]: 3

__

Why: The suggestion addresses a valid but unlikely edge case where the error response itself might fail to serialize. While correct, its impact is minor as this scenario is highly improbable.

Low
  • Update

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/unit/test_daemon_output_quality.py (1)

81-90: Strengthen the SWIG fallback assertion to validate exact behavior

Line 89 only checks type; asserting exact str(obj) output makes this test more regression-resistant.

Proposed test tightening
         obj = SwigObj()
         result = _enum_name(obj)
         assert isinstance(result, str)
+        assert result == str(obj)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/test_daemon_output_quality.py` around lines 81 - 90, The test
test_swig_fallback_returns_str should assert the exact fallback string value
rather than just the type: replace or augment the existing isinstance assertion
with an equality check that result == str(obj) to ensure _enum_name(obj) returns
the same string as str(obj) for objects without a .name (referencing the
_enum_name function and the SwigObj instance in the test).
tests/unit/test_daemon_server_unit.py (1)

541-569: Avoid duplicating fallback logic inside the test

This block re-implements the server’s serialization-guard branch. Prefer asserting behavior through shared helper logic (or an integration-style run_server path) so test and production cannot silently diverge.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/test_daemon_server_unit.py` around lines 541 - 569, The test
test_json_dumps_guard_produces_valid_error is re-implementing the server's
serialization-fallback logic; instead, call the actual serialization guard used
by run_server (or the shared helper the server uses to produce the error
payload) to get the payload and assert on it. Replace the try/except/json.dumps
block with an invocation of the server's serialization helper (the same function
run_server delegates to when catching TypeError) and assert that the returned
JSON contains id == 42, error.code == -32603 and that the error.message contains
"serialization error".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/rdc/daemon_server.py`:
- Around line 438-446: The except block currently only catches TypeError from
json.dumps, so other serialization failures (ValueError, RecursionError, or any
other Exception) can escape; update the exception handling around the json.dumps
call used to build err_resp (the block that sets err_resp, payload, and
binary_path) to catch ValueError and RecursionError (or catch Exception broadly)
and log the exception (_log.warning/_.error) and produce the same JSON-RPC error
response using response.get("id"), ensuring payload is set to
json.dumps(err_resp) + "\n" and binary_path is set to None; keep the same error
code/message structure but include the exception details in the message.

---

Nitpick comments:
In `@tests/unit/test_daemon_output_quality.py`:
- Around line 81-90: The test test_swig_fallback_returns_str should assert the
exact fallback string value rather than just the type: replace or augment the
existing isinstance assertion with an equality check that result == str(obj) to
ensure _enum_name(obj) returns the same string as str(obj) for objects without a
.name (referencing the _enum_name function and the SwigObj instance in the
test).

In `@tests/unit/test_daemon_server_unit.py`:
- Around line 541-569: The test test_json_dumps_guard_produces_valid_error is
re-implementing the server's serialization-fallback logic; instead, call the
actual serialization guard used by run_server (or the shared helper the server
uses to produce the error payload) to get the payload and assert on it. Replace
the try/except/json.dumps block with an invocation of the server's serialization
helper (the same function run_server delegates to when catching TypeError) and
assert that the returned JSON contains id == 42, error.code == -32603 and that
the error.message contains "serialization error".

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae29494 and 6651dbc.

📒 Files selected for processing (4)
  • src/rdc/daemon_server.py
  • src/rdc/handlers/_helpers.py
  • tests/unit/test_daemon_output_quality.py
  • tests/unit/test_daemon_server_unit.py

@greptile-apps
Copy link

greptile-apps bot commented Mar 1, 2026

Greptile Summary

This PR fixes a daemon crash (B59) caused by non-serializable SWIG objects reaching json.dumps in the request loop, which previously terminated the connection handler and cascaded into subsequent tests losing their session. Two complementary layers of defence are added: _enum_name() now converts unrecognised object types to str() before they reach JSON serialisation, and run_server() wraps json.dumps in a try/except (TypeError, ValueError, RecursionError) that returns a JSON-RPC -32603 error response instead of propagating the exception.

  • daemon_server.py: serialisation is split into a two-phase try/except; the error-response fallback always ensures payload is bound before conn.sendall, so the loop continues regardless of what a handler returns. All three exception types (TypeError, ValueError, RecursionError) identified in the prior review are now caught.
  • _helpers.py: _enum_name explicitly fast-paths primitives and None, then logs a debug-level warning before calling str() on anything else — addressing the prior review's request for observability around the silent coercion.
  • Tests: TestSerializationResilience and two new TestEnumName cases provide targeted coverage, though test_json_dumps_guard_produces_valid_error has a minor initialisation gap for payload_ok (see inline comment).
  • The _enum_name behavioural change silently stringifies container types (list, dict, tuple) that were previously passed through unchanged; no current call-site passes those types, so the practical impact is zero, but future callers should be aware.

Confidence Score: 4/5

  • Safe to merge — both production changes are correct and well-scoped; one minor test robustness issue is present but does not affect production behaviour.
  • The production fixes in daemon_server.py and _helpers.py are logically sound and address all three exception types from the prior review. The _enum_name change is backwards-compatible for all existing call-sites. The only gap is in the test: payload_ok is not initialised before the try/except in test_json_dumps_guard_produces_valid_error, which could yield a NameError rather than a clean assertion failure if an unexpected exception type were raised — a test-reliability concern, not a production bug.
  • tests/unit/test_daemon_server_unit.py — payload_ok initialisation gap in TestSerializationResilience

Important Files Changed

Filename Overview
src/rdc/daemon_server.py Splits the original single json.dumps call into a two-phase try/except pattern: the first block catches (TypeError, ValueError, RecursionError) and falls back to a JSON-RPC -32603 error payload; the second block does the sendall. All three exception types from the previous review are now handled, and payload is always bound before the sendall.
src/rdc/handlers/_helpers.py _enum_name now explicitly passes through primitives (str, int, float, None) and falls back to str() with a debug-level log for all other types. The logging import and module-level _log were added. Behavioural note: dict/list/tuple values previously passed through unchanged (and were JSON-serialisable) are now stringified; no current call-site passes those types so impact is negligible.
tests/unit/test_daemon_server_unit.py Adds TestSerializationResilience with two tests. test_json_dumps_guard_produces_valid_error has a minor robustness gap: payload_ok is not initialised before the try/except, so a non-TypeError exception (ValueError, RecursionError) from json.dumps would leave it unbound and produce a NameError at the assertion.
tests/unit/test_daemon_output_quality.py Two new tests cover the B59 fix: test_swig_fallback_returns_str validates that objects without .name are coerced to str, and test_float_passthrough confirms float primitives are unaffected. Both tests are clean and correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Handler returns result"] --> B{"_enum_name called?"}
    B -->|"has .name"| C["return v.name"]
    B -->|"str / int / float / None"| D["pass through"]
    B -->|"other type (SWIG obj etc.)"| E["log debug warning\nreturn str(v)"]
    C --> F["response dict assembled"]
    D --> F
    E --> F
    F --> G["json.dumps(response)"]
    G -->|"success"| H["payload = serialised JSON"]
    G -->|"TypeError / ValueError / RecursionError"| I["log warning\nbuild -32603 error response\nbinary_path = None"]
    I --> J["payload = json.dumps(err_resp)"]
    H --> K["conn.sendall(payload)"]
    J --> K
    K -->|"binary_path set"| L["send binary file"]
    K -->|"no binary"| M["continue loop"]
    L --> M
Loading

Last reviewed commit: 7c2ad5d

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 436 to 446
try:
conn.sendall((json.dumps(response) + "\n").encode("utf-8"))
payload = json.dumps(response) + "\n"
except TypeError as exc:
_log.warning("serialization error: %s", exc)
err_resp: dict[str, Any] = {
"jsonrpc": "2.0",
"id": response.get("id"),
"error": {"code": -32603, "message": f"serialization error: {exc}"},
}
payload = json.dumps(err_resp) + "\n"
binary_path = None
Copy link

Choose a reason for hiding this comment

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

ValueError not caught — daemon still crashes on circular references

json.dumps can also raise ValueError (e.g. for circular-reference structures or NaN/Infinity when allow_nan=False). The guard only catches TypeError, so a response containing a circular dict would still propagate an unhandled exception and terminate the connection handler — the exact crash the fix is trying to prevent.

Suggested change
try:
conn.sendall((json.dumps(response) + "\n").encode("utf-8"))
payload = json.dumps(response) + "\n"
except TypeError as exc:
_log.warning("serialization error: %s", exc)
err_resp: dict[str, Any] = {
"jsonrpc": "2.0",
"id": response.get("id"),
"error": {"code": -32603, "message": f"serialization error: {exc}"},
}
payload = json.dumps(err_resp) + "\n"
binary_path = None
payload = json.dumps(response) + "\n"
except (TypeError, ValueError) as exc:

Comment on lines 63 to 67
if hasattr(v, "name"):
return v.name
if isinstance(v, (str, int, float)) or v is None:
return v
return str(v)
Copy link

Choose a reason for hiding this comment

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

Silent str() conversion may hide unexpected object types

The original function returned v unchanged for any non-.name type, which meant the JSON serializer would surface the problem explicitly at the serialization boundary. The new fallback silently coerces any unrecognized object to a string, making it harder to detect cases where a handler is returning an unexpected type (e.g. a dict or list that was previously passed through and JSON-serialized correctly would now be stringified).

Consider logging a warning before the str() coercion so that unexpected types are surfaced during development:

    import logging as _logging
    _log = _logging.getLogger(__name__)
    _log.debug("_enum_name: unexpected type %s, coercing to str", type(v).__name__)
    return str(v)

All current call-sites use SWIG enums or empty-string defaults, so this is low risk in practice, but the silent coercion could mask future bugs.

Comment on lines +553 to +569
try:
json.dumps(response)
payload_ok = True
except TypeError as exc:
err_resp: dict[str, Any] = {
"jsonrpc": "2.0",
"id": response.get("id"),
"error": {"code": -32603, "message": f"serialization error: {exc}"},
}
payload = json.dumps(err_resp)
payload_ok = False

assert not payload_ok
parsed = json.loads(payload)
assert parsed["id"] == 42
assert parsed["error"]["code"] == -32603
assert "serialization error" in parsed["error"]["message"]
Copy link

Choose a reason for hiding this comment

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

payload may be unbound if the try block unexpectedly succeeds

payload is only assigned inside the except TypeError branch. If json.dumps(response) were ever to succeed (e.g. if Python's JSON encoder changes, or if Unserializable gains a __json__ method in a future version), payload_ok would be True, the assert not payload_ok would catch that — but the subsequent json.loads(payload) would raise UnboundLocalError before the assertion is even evaluated, producing a misleading test failure.

Initialise the variable before the try/except to make the test resilient:

        payload = ""
        try:
            json.dumps(response)
            payload_ok = True
        except TypeError as exc:
            ...

- Extend JSON serialization guard to also catch ValueError and RecursionError
- Add debug log in _enum_name before str() coercion of unexpected types
- Initialize payload variable before try/except to prevent UnboundLocalError
Comment on lines +554 to +564
try:
json.dumps(response)
payload_ok = True
except TypeError as exc:
err_resp: dict[str, Any] = {
"jsonrpc": "2.0",
"id": response.get("id"),
"error": {"code": -32603, "message": f"serialization error: {exc}"},
}
payload = json.dumps(err_resp)
payload_ok = False
Copy link

Choose a reason for hiding this comment

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

payload_ok unbound for non-TypeError exceptions

payload_ok is only assigned inside the try or except TypeError branches. If json.dumps(response) were to raise ValueError or RecursionError (both of which are caught in the production guard), neither branch would execute, leaving payload_ok unbound. The subsequent assert not payload_ok would then raise a misleading NameError rather than a clean test failure.

Note: the production code in run_server now catches (TypeError, ValueError, RecursionError), so the test surface is slightly narrower than production. Initialising payload_ok before the block makes the test resilient to that mismatch:

Suggested change
try:
json.dumps(response)
payload_ok = True
except TypeError as exc:
err_resp: dict[str, Any] = {
"jsonrpc": "2.0",
"id": response.get("id"),
"error": {"code": -32603, "message": f"serialization error: {exc}"},
}
payload = json.dumps(err_resp)
payload_ok = False
payload = ""
payload_ok = True
try:
json.dumps(response)
except TypeError as exc:
err_resp: dict[str, Any] = {
"jsonrpc": "2.0",
"id": response.get("id"),
"error": {"code": -32603, "message": f"serialization error: {exc}"},
}
payload = json.dumps(err_resp)
payload_ok = False

@BANANASJIM BANANASJIM merged commit fca28ce into master Mar 1, 2026
18 checks passed
@BANANASJIM BANANASJIM deleted the fix/b59-descriptors-crash branch March 1, 2026 08:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant