Repository navigation
Description
Summary
timed_with_status in src/memos/utils.py catches every exception, but when no fallback callable is configured the wrapper falls through to an implicit return None. Decorated functions therefore return None on any failure instead of raising, which masks the real error.
Where
src/memos/utils.py, lines 43-56 (current main @ cddc252).
try:
result = fn(*args, **kwargs)
success_flag = True
return result
except Exception as e:
exc_type = type(e)
stack_info = "".join(traceback.format_stack()[:-1])
exc_message = f"{stack_info}{traceback.format_exc()}"
success_flag = False
if fallback is not None and callable(fallback):
result = fallback(e, *args, **kwargs)
return result
# ← no `raise` here; wrapper falls through to implicit return None
finally:
...Impact
OpenAILLM.generate is decorated with @timed_with_status(...). When the upstream LLM returns 4xx/5xx (e.g. MiniMax 400 chat content is empty (2013) for a system-only message), the BadRequestError is caught, logged as status: FAILED, and then swallowed. generate() returns None to its caller.
Downstream that None flows into clean_json_response(response) (src/memos/mem_os/utils/format_utils.py:1403) and crashes with:
AttributeError: 'NoneType' object has no attribute 'replace'
Two consequences:
- The user sees a confusing AttributeError instead of the real 400 from the LLM. Diagnosis is hard because nothing in the traceback names the LLM call.
- It is a silent fail by API contract. Callers cannot tell whether
generate()succeeded with empty output or failed with an exception, because the sameNonerepresents both.
Reproduction
- Set
MOS_CHAT_MODEL=MiniMax-M2.7,OPENAI_API_BASE=https://api.minimax.io/v1, validOPENAI_API_KEY. - Start memos server, ensure default cube exists.
POST /product/suggestionswith amem_cube_idwhose recent memories are empty (so the suggestion prompt has only a system message).- Observe HTTP 500
'NoneType' object has no attribute 'replace'in the response, and[TIMER_WITH_STATUS] OpenAI LLM took 5051 ms, status: FAILED, error_type: BadRequestError, error_message: ...immediately above it in the log.
Proposed fix
Add an explicit raise after the fallback branch:
if fallback is not None and callable(fallback):
result = fallback(e, *args, **kwargs)
return result
raise
finally:
...This preserves existing fallback semantics and makes the no-fallback path fail-fast.
I will open a PR with this change against main.
Related
The same swallow likely contributes to other reports where downstream code receives unexpected None/empty values from LLM helpers (e.g. #1324 memory_search always returns no results with reasoning-enabled models — different root cause, but the same pattern of LLM-call failure being invisible to the caller).
Activity
MaiHHConnect commentedon Apr 23, 2026
Memtensor-AI commentedon May 28, 2026
✅ AutoDev task
702c33102691b25fcompleted.Summary: 修复 #1523:
timed_with_status在未配置fallback时会吞掉异常返回None,导致下游clean_json_response抛出误导性的AttributeError。在 fallback 分支后追加显式raise,让无 fallback 路径 fail-fast,同时保留原有 fallback 语义与status: FAILED日志。已更新test_failure_logging_no_fallback使其断言异常会冒泡,并新增test_no_fallback_does_not_return_none防止再回归。已通过独立脚本验证三条路径(无 fallback 抛出 / 有 fallback 返回 / 成功返回原值)行为均符合预期。Base branch:
v2.0.16Branch:
autodev/MemOS-1523Commit:
249ac53PR: #1826
Assigned to: @CarltonXiang, @syzsunshine219
Reviewers: @CarltonXiang, @syzsunshine219
Memtensor-AI commentedon Jun 4, 2026
🤖 AutoDev has picked up this issue and started working on it.
Task ID:
d84eb4283983e6d4Working branch:
bugfix/autodev-1523Target branch:
dev-20260604-v2.0.19Workflow: opsp (analysis → coding → testing → PR)
I will post the PR link here once done. If I need more information, I will ask in the comments.
fix: re-raise exceptions in timed_with_status when no fallback config…
Memtensor-AI commentedon Jun 4, 2026
✅ AutoDev task
d84eb4283983e6d4completed.Summary: Successfully fixed issue #1523:
timed_with_statusdecorator now correctly re-raises exceptions when no fallback is configured, preventing silent failures.What Changed
Root Cause: The
timed_with_statusdecorator insrc/memos/utils.pywas catching all exceptions but silently swallowing them when no fallback was provided. This caused decorated functions likeOpenAILLM.generate()to returnNoneinstead of propagating errors, masking real failures with confusing downstream errors.Fix Applied: Added a single
raisestatement at line 182, immediately after the fallback branch. This ensures exceptions are re-raised when no fallback is configured, while preserving existing fallback behavior.Code Change:
Testing
Created comprehensive regression test
tests/test_utils_timing_exception_reraise.pywith 4 test cases:Verified with standalone test script - all tests passed.
Impact
Before:
OpenAILLM.generate()returningNoneon LLM errors causedAttributeError: 'NoneType' object has no attribute 'replace'in downstream code, hiding the real BadRequestError.After: Original exceptions propagate correctly with meaningful tracebacks, enabling proper error handling and debugging.
Compatibility
fallbackparameter: No changefallback: Now correctly raise exceptions (this is the intended fail-fast behavior)The fix affects 3 existing uses in the codebase:
openai.py(2 uses): Now correctly propagate LLM errorsuniversal_api.py(1 use): Now correctly propagate embedding errorshttp_bge.py(1 use with fallback): Behavior unchangedBase branch:
dev-20260604-v2.0.19Branch:
bugfix/autodev-1523Commit:
8d0c04bPR: #1885
Assigned to: @MatthewZhuang, @CarltonXiang, @syzsunshine219
Reviewers: @MatthewZhuang, @CarltonXiang, @syzsunshine219
Fix #1523: [Bug] timed_with_status decorator silently swallows except…