Fix: lfx run agent _noopresult not iterable error#10892
Conversation
WalkthroughTwo changes addressing async handling and iteration support: modified Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 error, 3 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
Codecov Report❌ Patch coverage is
❌ Your project check has failed because the head coverage (39.95%) is below the target coverage (60.00%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #10892 +/- ##
==========================================
- Coverage 32.54% 32.53% -0.02%
==========================================
Files 1370 1370
Lines 63543 63549 +6
Branches 9394 9393 -1
==========================================
- Hits 20680 20674 -6
- Misses 41824 41835 +11
- Partials 1039 1040 +1
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/backend/base/langflow/memory.py (1)
167-167: Consider applying the same coroutine check here for consistency.Line 167 calls
session.add(msg)without checking if the result is a coroutine, while lines 193-195 inaadd_messagetablesdo include this check. For consistency and to prevent potential warnings when usingNoopSession, consider applying the same pattern here.Apply this diff to maintain consistency:
- session.add(msg) + result = session.add(msg) + if asyncio.iscoroutine(result): + await result
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/backend/base/langflow/memory.py(1 hunks)src/lfx/src/lfx/services/session.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/backend/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
src/backend/**/*.py: Use FastAPI async patterns withawaitfor async operations in component execution methods
Useasyncio.create_task()for background tasks and implement proper cleanup with try/except forasyncio.CancelledError
Usequeue.put_nowait()for non-blocking queue operations andasyncio.wait_for()with timeouts for controlled get operations
Files:
src/backend/base/langflow/memory.py
🧠 Learnings (1)
📚 Learning: 2025-11-24T19:46:09.104Z
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/backend_development.mdc:0-0
Timestamp: 2025-11-24T19:46:09.104Z
Learning: Applies to src/backend/base/langflow/components/**/*.py : For component `run()` methods, use async/await pattern and return appropriate message types
Applied to files:
src/backend/base/langflow/memory.py
🧬 Code graph analysis (1)
src/backend/base/langflow/memory.py (2)
src/backend/base/langflow/services/task/backends/anyio.py (1)
result(36-37)src/lfx/src/lfx/services/session.py (1)
add(27-28)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (17)
- GitHub Check: Validate PR
- GitHub Check: Test Docker Images / Test docker images
- GitHub Check: Lint Backend / Run Mypy (3.10)
- GitHub Check: Lint Backend / Run Mypy (3.11)
- GitHub Check: Lint Backend / Run Mypy (3.12)
- GitHub Check: Test Starter Templates
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 4
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 5
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 2
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 3
- GitHub Check: Run Backend Tests / LFX Tests - Python 3.10
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 1
- GitHub Check: Run Backend Tests / Integration Tests - Python 3.10
- GitHub Check: Update Component Index
- GitHub Check: Update Starter Projects
- GitHub Check: Run Ruff Check and Format
- GitHub Check: Optimize new Python code in this PR
🔇 Additional comments (1)
src/lfx/src/lfx/services/session.py (1)
71-72: LGTM! Correctly implements iteration support.The
__iter__method properly returns an empty iterator, which is consistent with theall()method returning an empty list. This fixes the "'_NoopResult' object is not iterable" error while maintaining the no-op pattern.
| result = session.add(message) | ||
| if asyncio.iscoroutine(result): | ||
| await result |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n src/backend/base/langflow/memory.py | sed -n '160,200p'Repository: langflow-ai/langflow
Length of output: 2211
🏁 Script executed:
rg -n 'session\.add\(' --type=py src/backend/base/langflow/memory.py | head -20Repository: langflow-ai/langflow
Length of output: 150
🏁 Script executed:
rg -n -B2 -A2 'session\.add\(' --type=py src/backend/base/langflow/memory.pyRepository: langflow-ai/langflow
Length of output: 496
🏁 Script executed:
rg -n 'class NoopSession' --type=py src/backend/Repository: langflow-ai/langflow
Length of output: 139
🏁 Script executed:
rg -n -A10 'class NoopSession' --type=py src/backend/Repository: langflow-ai/langflow
Length of output: 1010
🏁 Script executed:
rg -n '\.add\(' --type=py src/backend/base/langflow/memory.pyRepository: langflow-ai/langflow
Length of output: 150
🏁 Script executed:
cat -n src/backend/base/langflow/services/database/session.py | head -100Repository: langflow-ai/langflow
Length of output: 1856
🏁 Script executed:
rg -n -A5 'def add\(' src/backend/base/langflow/services/database/session.pyRepository: langflow-ai/langflow
Length of output: 179
🌐 Web query:
SQLAlchemy AsyncSession add method synchronous or asynchronous
💡 Result:
AsyncSession.add is a synchronous (non-awaitable) method — it proxies the regular Session.add and is called without await. [1][2]
Sources:
- SQLAlchemy asyncio docs — AsyncSession.add (proxied, not async). [1]
- SQLAlchemy 1.4 asyncio docs / examples showing session.add used without await. [2]
🏁 Script executed:
rg -n 'def aupdate_messages' --type=py src/backend/base/langflow/memory.py -A30Repository: langflow-ai/langflow
Length of output: 1608
🏁 Script executed:
rg -n 'def session_scope' --type=py src/backend/ -A10Repository: langflow-ai/langflow
Length of output: 996
🏁 Script executed:
rg -n 'NoopSession' --type=py src/backend/ | head -20Repository: langflow-ai/langflow
Length of output: 488
🏁 Script executed:
cat -n src/backend/base/langflow/services/deps.py | sed -n '150,200p'Repository: langflow-ai/langflow
Length of output: 1981
🏁 Script executed:
cat -n src/backend/base/langflow/services/database/service.py | sed -n '210,230p'Repository: langflow-ai/langflow
Length of output: 1169
🏁 Script executed:
rg -n 'session\.(add|commit|delete)' --type=py src/backend/base/langflow/memory.pyRepository: langflow-ai/langflow
Length of output: 319
🏁 Script executed:
grep -n 'use_noop_database' src/backend/base/langflow/services/database/service.pyRepository: langflow-ai/langflow
Length of output: 128
Apply the same coroutine check to line 167.
The fix at lines 193–195 correctly handles the API mismatch where NoopSession.add() is async (returns a coroutine) while AsyncSession.add() is sync. However, line 167 has an identical unchecked session.add(msg) call that will produce a "coroutine never awaited" warning when use_noop_database=True.
Update line 167–168 to:
result = session.add(msg)
if asyncio.iscoroutine(result):
await resultThis ensures consistent handling across both functions since both can receive either AsyncSession or NoopSession at runtime.
🤖 Prompt for AI Agents
In src/backend/base/langflow/memory.py around lines 167 to 168, the call to
session.add(msg) may return a coroutine when using NoopSession and currently
isn't awaited; change the code to capture the result (result =
session.add(msg)), check if it's a coroutine with asyncio.iscoroutine(result),
and await it if so to avoid "coroutine never awaited" warnings and match the fix
made at lines 193–195.
There was a problem hiding this comment.
This looks relevant.
But also - why not just make noopsession sync if the impls are noop?
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find where NoopSession is used and how it's instantiated
rg -n 'NoopSession' --type=py src/backend/ -B2 -A2
Length of output: 1921
🏁 Script executed:
#!/bin/bash
# Look at the session_scope implementation to understand the contract
cat src/backend/base/langflow/services/deps.py | sed -n '1,250p'
Length of output: 7723
🏁 Script executed:
#!/bin/bash
# Check if NoopSession methods are awaited anywhere
rg -n 'await.*noop|await.*NoopSession' --type=py src/backend/
Length of output: 0
🏁 Script executed:
#!/bin/bash
# Look for the full NoopSession implementation
cat src/lfx/src/lfx/services/session.py
Length of output: 2090
@jordanrfrazier You're absolutely right! This is a design issue with NoopSession.
Looking at the implementation, NoopSession.add() is async but AsyncSession.add() is synchronous. This API mismatch is the root cause:
# SQLAlchemy's AsyncSession
session.add(obj) # Sync method, returns None
# NoopSession (current implementation)
async def add(self, *args, **kwargs): # Returns a coroutine!
passThe better fix would be to make NoopSession.add() synchronous to match the actual API it's mocking:
def add(self, *args, **kwargs): # Remove async
passThis would:
- ✅ Eliminate the need for coroutine checks throughout the codebase
- ✅ Make NoopSession a proper drop-in replacement for AsyncSession
- ✅ Prevent future RuntimeWarnings
- ✅ Simplify the code
Important: Other NoopSession methods like commit(), rollback(), refresh(), get(), exec() should remain async because they're actually awaited in SQLAlchemy code and match AsyncSession's async methods.
The current fix (checking asyncio.iscoroutine()) is a workaround that should be applied to both lines 167 and 193 if you keep the current NoopSession design. But refactoring NoopSession.add() to be sync would be cleaner and eliminate this class of bugs entirely.
Would you like me to help create an issue to track refactoring NoopSession.add() to be synchronous?
🧬 Code Graph Analysis Results
[src/lfx/src/lfx/services/session.py] lines 36-37
def result(self) -> Any:
return self._result
[src/lfx/src/lfx/services/session.py] lines 27-28
async def add(self, *args, **kwargs):
pass
[src/lfx/src/lfx/services/session.py] lines 30-31
async def commit(self):
pass
[src/lfx/src/lfx/services/session.py] lines 33-34
async def rollback(self):
pass
[src/lfx/src/lfx/services/session.py] lines 45-46
async def refresh(self, *args, **kwargs):
pass
[src/lfx/src/lfx/services/session.py] lines 60-74
async def get(self, *args, **kwargs): # noqa: ARG002
return None
[src/lfx/src/lfx/services/session.py] lines 60-74
async def exec(self, *args, **kwargs): # noqa: ARG002
class _NoopResult:
def first(self):
return None
def all(self):
return []
def one_or_none(self):
return None
def __iter__(self):
return iter([])
return _NoopResult()
[src/lfx/src/lfx/services/deps.py] lines 133-171
async def session_scope() -> AsyncGenerator[AsyncSession, None]:
"""Context manager for managing an async session scope with auto-commit for write operations.
This is used with `async with session_scope() as session:` for direct session management.
It ensures that the session is properly committed if no exceptions occur,
and rolled back if an exception is raised.
Use session_scope_readonly() for read-only operations to avoid unnecessary commits and locks.
Yields:
AsyncSession: The async session object.
Raises:
Exception: If an error occurs during the session scope.
"""
db_service = get_db_service()
async with db_service._with_session() as session: # noqa: SLF001
try:
yield session
await session.commit()
except Exception as e:
# Log at appropriate level based on error type
if isinstance(e, HTTPException):
if HTTPStatus.BAD_REQUEST.value <= e.status_code < HTTPStatus.INTERNAL_SERVER_ERROR.value:
# Client errors (4xx) - log at info level
await logger.ainfo(f"Client error during session scope: {e.status_code}: {e.detail}")
else:
# Server errors (5xx) or other - log at error level
await logger.aexception("An error occurred during the session scope.", exception=e)
else:
# Non-HTTP exceptions - log at error level
await logger.aexception("An error occurred during the session scope.", exception=e)
# Only rollback if session is still in a valid state
if session.is_active:
with suppress(InvalidRequestError):
# Session was already rolled back by SQLAlchemy
await session.rollback()
raise
# No explicit close needed - _with_session() handles it
🧠 Learnings used
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/backend_development.mdc:0-0
Timestamp: 2025-11-24T19:46:09.104Z
Learning: Applies to src/backend/**/*.py : Use `queue.put_nowait()` for non-blocking queue operations and `asyncio.wait_for()` with timeouts for controlled get operations
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/backend_development.mdc:0-0
Timestamp: 2025-11-24T19:46:09.104Z
Learning: Applies to src/backend/base/langflow/components/**/*.py : For component `run()` methods, use async/await pattern and return appropriate message types
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/backend_development.mdc:0-0
Timestamp: 2025-11-24T19:46:09.104Z
Learning: Applies to src/backend/**/*.py : Use FastAPI async patterns with `await` for async operations in component execution methods
83c1e9d to
2321216
Compare
lfx run [path-to-flow-containing-agent] [user-prompt-input]throws the following exceptionwhich is fixed but adding an iter method to the _NoopResult class.
After which the same command throws the following warning:
which is fixed by making the
addmethod stub sync.Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.