Skip to content

fix(traceloop-sdk): avoid calling async json methods in JSONEncoder#3968

Open
trinhchien wants to merge 2 commits intotraceloop:mainfrom
trinhchien:fix-3967-async-request-json
Open

fix(traceloop-sdk): avoid calling async json methods in JSONEncoder#3968
trinhchien wants to merge 2 commits intotraceloop:mainfrom
trinhchien:fix-3967-async-request-json

Conversation

@trinhchien
Copy link
Copy Markdown

@trinhchien trinhchien commented Apr 9, 2026

Summary

  • avoid calling async .json() methods during input serialization
  • keep workflow tracing intact by falling back to class-name serialization for async-only request-like objects
  • add a regression test for async workflows receiving an argument with an async json() method

Why

JSONEncoder currently calls any .json() method it finds synchronously. For objects like Starlette/FastAPI Request, json() is async, which leads to RuntimeWarning: coroutine ... was never awaited during workflow input serialization.

Testing

  • pytest packages/traceloop-sdk/tests/test_workflows.py -q -k 'async_json_method_argument'
  • pytest packages/traceloop-sdk/tests/test_workflows.py -q -k 'dataclass_serialization_workflow or async_json_method_argument'

Summary by CodeRabbit

  • Bug Fixes

    • Improved JSON serialization during tracing to correctly handle objects exposing a JSON method, avoiding accidental awaiting of async callables and preventing incorrect serialization.
  • Tests

    • Added sync and async workflow tests to verify argument serialization and span input/output recording when workflow arguments expose a JSON method.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 9, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


trinhchien seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

Add synchronous and asynchronous tests that exercise workflow argument serialization for objects exposing a json method; update JSONEncoder.default to call an object's json attribute only when it's callable, not an async coroutine function, and when the invocation does not return a coroutine.

Changes

Cohort / File(s) Summary
Workflow tests
packages/traceloop-sdk/tests/test_workflows.py
Added FakeAsyncRequest (async def json) and FakeSyncRequest (def json) plus two tests: an @pytest.mark.asyncio async workflow test that awaits the workflow and asserts a single request_workflow.workflow span with input ["FakeAsyncRequest"] and output {"ok": True}, and a sync workflow test asserting input [{"ok": True}] and output {"ok": True}.
JSON encoder logic
packages/traceloop-sdk/traceloop/sdk/utils/json_encoder.py
Changed JSONEncoder.default to treat o.json as a potential callable: retrieve o.json, ensure it's callable and not an async coroutine function, invoke it, and return the result only if the invocation does not produce a coroutine (avoids unconditionally calling async json).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hop through tests with eager cheer,
Async and sync friends drew near,
Encoder asks before it calls,
No coroutine stumbles or falls,
Tiny paws tidy spans — huzzah!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 accurately describes the main change: fixing the JSONEncoder to avoid calling async json methods synchronously during input serialization.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/traceloop-sdk/traceloop/sdk/utils/json_encoder.py`:
- Around line 18-23: The branch handling hasattr(o, "json") currently only
returns a value when o.json is callable; to preserve existing behavior you must
also handle non-callable json attributes: keep the current callable path (use
json_method = o.json, call it when callable and not inspect.iscoroutinefunction,
and return result if not inspect.iscoroutine(result)), and add an else branch
that returns the non-callable o.json value directly (without calling) so it
doesn't fall through to the class-name fallback; ensure these checks use the
same symbols (hasattr(o, "json"), json_method, inspect.iscoroutinefunction,
inspect.iscoroutine) and place this logic before the fallback that returns the
object's class name.
🪄 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

Run ID: 316c3afc-d5d3-4e32-8875-d4d79cc263d2

📥 Commits

Reviewing files that changed from the base of the PR and between 786d49f and c1a3566.

📒 Files selected for processing (2)
  • packages/traceloop-sdk/tests/test_workflows.py
  • packages/traceloop-sdk/traceloop/sdk/utils/json_encoder.py

Copy link
Copy Markdown
Contributor

@max-deygin-traceloop max-deygin-traceloop left a comment

Choose a reason for hiding this comment

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

If possible, please add a regression test for the sync json() method.
This is minor, we can merge without it.

@trinhchien trinhchien force-pushed the fix-3967-async-request-json branch from 53babf9 to 625bb89 Compare April 12, 2026 13:20
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
packages/traceloop-sdk/tests/test_workflows.py (1)

511-528: Strengthen regression by asserting no unawaited-coroutine warning.

The current assertions validate serialized values, but the original bug was the RuntimeWarning side effect. Add an explicit warning assertion so this cannot regress silently.

Proposed test hardening
 `@pytest.mark.asyncio`
-async def test_async_workflow_with_async_json_method_argument(exporter):
+async def test_async_workflow_with_async_json_method_argument(exporter, recwarn):
     `@workflow`(name="request_workflow")
     async def request_workflow(request: FakeAsyncRequest):
         return {"ok": True}

     await request_workflow(FakeAsyncRequest())
+    assert not any(
+        w.category is RuntimeWarning and "was never awaited" in str(w.message)
+        for w in recwarn
+    )

     spans = exporter.get_finished_spans()
     assert [span.name for span in spans] == ["request_workflow.workflow"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/traceloop-sdk/tests/test_workflows.py` around lines 511 - 528, Wrap
the call to await request_workflow(FakeAsyncRequest()) inside a warnings-capture
context and assert no RuntimeWarning was emitted so the regression can't slip
by; specifically modify the test_async_workflow_with_async_json_method_argument
test to capture warnings (e.g., using warnings.catch_warnings(record=True) or
pytest.warns(None) around the await) and then assert that none of the captured
warnings have category RuntimeWarning, keeping the rest of the assertions
(exporter spans, JSON attributes) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/traceloop-sdk/tests/test_workflows.py`:
- Around line 511-528: Wrap the call to await
request_workflow(FakeAsyncRequest()) inside a warnings-capture context and
assert no RuntimeWarning was emitted so the regression can't slip by;
specifically modify the test_async_workflow_with_async_json_method_argument test
to capture warnings (e.g., using warnings.catch_warnings(record=True) or
pytest.warns(None) around the await) and then assert that none of the captured
warnings have category RuntimeWarning, keeping the rest of the assertions
(exporter spans, JSON attributes) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f789923d-7462-4c8a-aa33-25620db1c49c

📥 Commits

Reviewing files that changed from the base of the PR and between 625bb89 and d2bf3b2.

📒 Files selected for processing (1)
  • packages/traceloop-sdk/tests/test_workflows.py

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.

3 participants