Skip to content

Fix: lfx run agent _noopresult not iterable error#10892

Closed
HzaRashid wants to merge 2 commits into
mainfrom
fix/lfx-run-agent-memory
Closed

Fix: lfx run agent _noopresult not iterable error#10892
HzaRashid wants to merge 2 commits into
mainfrom
fix/lfx-run-agent-memory

Conversation

@HzaRashid
Copy link
Copy Markdown
Collaborator

@HzaRashid HzaRashid commented Dec 4, 2025

lfx run [path-to-flow-containing-agent] [user-prompt-input] throws the following exception

Error running method "message_response": '_NoopResult' object is not iterable
{"success": false, "type": "error", "exception_type": "ComponentBuildError", "exception_message": "Error building Component Agent: \n\nError running method \"message_response\": '_NoopResult' object is not iterable"}

which is fixed but adding an iter method to the _NoopResult class.

After which the same command throws the following warning:

/workspace/src/backend/base/langflow/memory.py:193: RuntimeWarning: coroutine 'NoopSession.add' was never awaited..."

which is fixed by making the add method stub sync.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of asynchronous message operations to ensure messages are properly queued and processed
    • Fixed iteration support for session result objects to prevent errors when traversing results

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 4, 2025

Walkthrough

Two changes addressing async handling and iteration support: modified add_messagetables to await coroutines returned from add operations; added __iter__ method to _NoopResult class for empty iteration support.

Changes

Cohort / File(s) Summary
Async message add handling
src/backend/base/langflow/memory.py
Modified add_messagetables to capture add operation results in a variable and explicitly await them if they return coroutines, replacing unconditional session.add(message) calls.
Noop result iteration
src/lfx/src/lfx/services/session.py
Added __iter__ method to the _NoopResult inner class returned by NoopSession.exec, returning an empty iterator to support iteration protocol.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

  • src/backend/base/langflow/memory.py: Verify the coroutine detection and await logic handles both sync and async add operations correctly
  • src/lfx/src/lfx/services/session.py: Confirm __iter__ returning empty iterator is the intended behavior for noop operations

Pre-merge checks and finishing touches

❌ Failed checks (1 error, 3 warnings)
Check name Status Explanation Resolution
Test Coverage For New Implementations ❌ Error PR modifies memory.py and session.py to fix asyncio and iteration issues but includes no test coverage for these critical changes. Add test_memory.py for add_message_tables async handling and tests for _NoopResult iteration; include integration test for lfx run with agents.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Test Quality And Coverage ⚠️ Warning PR implements critical runtime error fixes but lacks test coverage for new iter method, asyncio.iscoroutine logic, and line 167 unfixed duplicate call. Add unit test for _NoopResult.iter(), async tests for asyncio.iscoroutine logic with mocked sessions, fix line 167, and add corresponding tests.
Test File Naming And Structure ⚠️ Warning Backend _NoopResult lacks iter method while LFX version has it, creating inconsistent implementations that could cause 'not iterable' errors in backend code. Add iter method to backend _NoopResult class and add comprehensive async tests using @pytest.mark.asyncio to verify both implementations are iterable and work correctly.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main issue fixed in this PR: adding an iter method to _NoopResult and handling async add operations, making it clear and specific about the primary changes.
Excessive Mock Usage Warning ✅ Passed PR modifies only production code to fix runtime bugs; no test files modified; existing tests use appropriate patterns without excessive mocking.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/lfx-run-agent-memory

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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 4, 2025

Frontend Unit Test Coverage Report

Coverage Summary

Lines Statements Branches Functions
Coverage: 15%
15.45% (4251/27504) 8.62% (1812/21011) 9.7% (588/6059)

Unit Test Results

Tests Skipped Failures Errors Time
1671 0 💤 0 ❌ 0 🔥 21.546s ⏱️

@codecov
Copy link
Copy Markdown

codecov Bot commented Dec 4, 2025

Codecov Report

❌ Patch coverage is 40.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 32.53%. Comparing base (21e257a) to head (83c1e9d).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/lfx/src/lfx/services/session.py 0.00% 2 Missing ⚠️
src/backend/base/langflow/memory.py 66.66% 1 Missing ⚠️

❌ 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

Impacted file tree graph

@@            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     
Flag Coverage Δ
backend 51.49% <66.66%> (-0.08%) ⬇️
frontend 14.30% <ø> (+0.01%) ⬆️
lfx 39.95% <0.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/backend/base/langflow/memory.py 62.50% <66.66%> (-1.36%) ⬇️
src/lfx/src/lfx/services/session.py 58.18% <0.00%> (-2.20%) ⬇️

... and 7 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@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 (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 in aadd_messagetables do include this check. For consistency and to prevent potential warnings when using NoopSession, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2f6f6cd and b0115ca.

📒 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 with await for async operations in component execution methods
Use asyncio.create_task() for background tasks and implement proper cleanup with try/except for asyncio.CancelledError
Use queue.put_nowait() for non-blocking queue operations and asyncio.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 the all() method returning an empty list. This fixes the "'_NoopResult' object is not iterable" error while maintaining the no-op pattern.

Comment thread src/backend/base/langflow/memory.py Outdated
Comment on lines +193 to +195
result = session.add(message)
if asyncio.iscoroutine(result):
await result
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Dec 4, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 -20

Repository: langflow-ai/langflow

Length of output: 150


🏁 Script executed:

rg -n -B2 -A2 'session\.add\(' --type=py src/backend/base/langflow/memory.py

Repository: 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.py

Repository: langflow-ai/langflow

Length of output: 150


🏁 Script executed:

cat -n src/backend/base/langflow/services/database/session.py | head -100

Repository: langflow-ai/langflow

Length of output: 1856


🏁 Script executed:

rg -n -A5 'def add\(' src/backend/base/langflow/services/database/session.py

Repository: 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 -A30

Repository: langflow-ai/langflow

Length of output: 1608


🏁 Script executed:

rg -n 'def session_scope' --type=py src/backend/ -A10

Repository: langflow-ai/langflow

Length of output: 996


🏁 Script executed:

rg -n 'NoopSession' --type=py src/backend/ | head -20

Repository: 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.py

Repository: langflow-ai/langflow

Length of output: 319


🏁 Script executed:

grep -n 'use_noop_database' src/backend/base/langflow/services/database/service.py

Repository: 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 result

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This looks relevant.

But also - why not just make noopsession sync if the impls are noop?

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.

🧩 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!
    pass

The better fix would be to make NoopSession.add() synchronous to match the actual API it's mocking:

def add(self, *args, **kwargs):  # Remove async
    pass

This 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DO NOT MERGE Don't Merge this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants