Skip to content

Fix unbounded chat history leading to OOM (fixes #447) - #492

Closed
atul-upadhyay-7 wants to merge 2 commits into
FireFistisDead:masterfrom
atul-upadhyay-7:fix-chat-history-oom-447
Closed

Fix unbounded chat history leading to OOM (fixes #447)#492
atul-upadhyay-7 wants to merge 2 commits into
FireFistisDead:masterfrom
atul-upadhyay-7:fix-chat-history-oom-447

Conversation

@atul-upadhyay-7

@atul-upadhyay-7 atul-upadhyay-7 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Closes #447

This PR adds a MAX_CHAT_HISTORY_SIZE configurable limit to prevent out-of-memory crashes due to unbounded chat histories. The limit is enforced across the active session memory, background persistence, and on-startup loads to ensure robust and bounded memory usage.

Summary by CodeRabbit

  • New Features

    • Added a configurable limit for how much chat history is kept per session.
  • Bug Fixes

    • Session chat logs are now automatically trimmed so they don’t grow without bound.
    • Recent conversation history is preserved when sessions are loaded and saved, while older exchanges are dropped.

Copilot AI review requested due to automatic review settings June 7, 2026 12:50
@vercel

vercel Bot commented Jun 7, 2026

Copy link
Copy Markdown

@atul-upadhyay-7 is attempting to deploy a commit to the firefistisdead's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a configurable MAX_CHAT_HISTORY_SIZE environment variable (default 100) and enforces it by truncating chat history lists to the most recent MAX_CHAT_HISTORY_SIZE * 2 entries across session loading, in-memory appending, and persistence in rag-service/main.py, with a supporting test.

Changes

Bounded Chat History

Layer / File(s) Summary
Config constant and env documentation
rag-service/main.py, .env.example
Adds MAX_CHAT_HISTORY_SIZE env-configurable constant (default 100) and documents it in .env.example.
Truncation on session load
rag-service/main.py
load_sessions() truncates chat lists from sessions.json and per-session session_meta.json to the most recent MAX_CHAT_HISTORY_SIZE * 2 entries.
Truncation on append and persistence
rag-service/main.py
append_chat_exchange() trims in-memory chat after appending; save_sessions_unlocked() and _snapshot_session_for_persistence() persist only the trimmed history.
Test coverage
rag-service/test_main.py
Adds a test verifying truncation behavior on append, plus a commented-out skeleton test for PDF page-limit worker error handling.

Estimated code review effort: 2 (Simple) | ~15 minutes

Suggested labels: type:testing, docs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description has a summary and linked issue, but it omits most required template sections like Testing, Checklist, Notes, and Security. Expand the PR description to fill the template sections for Testing, Checklist, Notes, Screenshots, and Security, or mark them not applicable.
Out of Scope Changes check ⚠️ Warning The commented-out page-limit test skeleton appears unrelated to the chat-history OOM fix and is outside the linked issue scope. Remove the unrelated test skeleton or move it to a separate PR focused on PDF extraction/page-limit behavior.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the PR’s main change: capping chat history to prevent OOM.
Linked Issues check ✅ Passed The changes implement the requested chat-history cap in memory, persistence, startup loading, config, and tests for issue #447.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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.

@github-actions github-actions Bot added bug Something isn't working feature A new feature or improvement fix A targeted fix or cleanup rag-service FastAPI / model service work labels Jun 7, 2026

Copilot AI left a comment

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a configurable cap on per-session chat history and enforces that cap when loading, appending, and persisting sessions to prevent unbounded growth.

Changes:

  • Introduce MAX_CHAT_HISTORY_SIZE env var and document it in .env.example
  • Truncate chat history to MAX_CHAT_HISTORY_SIZE exchanges during session load/save/snapshot and on append_chat_exchange
  • Add a unit test for truncation behavior; disable an existing PDF worker test

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
rag-service/main.py Enforces chat history truncation across load/save/snapshot and when appending new exchanges; adds MAX_CHAT_HISTORY_SIZE config
rag-service/test_main.py Adds truncation test; comments out PDF worker test and removes worker import
.env.example Documents MAX_CHAT_HISTORY_SIZE env var

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread rag-service/main.py
Comment thread rag-service/main.py
Comment thread rag-service/test_main.py
Comment thread .env.example

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
rag-service/main.py (1)

183-186: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated truncation expression into a helper.

The chat[-(MAX_CHAT_HISTORY_SIZE * 2):] pattern (and the associated length check) is duplicated across five sites (load, overlay, save, append, snapshot). Any future change to the truncation policy (e.g., switching from *2 to counting user/bot exchanges explicitly) requires editing all five in lockstep, and it's easy to miss one.

♻️ Proposed helper
+def _cap_chat_history(chat: list) -> list:
+    """Return chat trimmed to the most recent MAX_CHAT_HISTORY_SIZE exchanges."""
+    max_entries = MAX_CHAT_HISTORY_SIZE * 2
+    return chat[-max_entries:] if len(chat) > max_entries else chat

Then replace each occurrence, e.g.:

-                    chat_history = normalize_chat_history(meta.get("chat", []))
-                    if len(chat_history) > MAX_CHAT_HISTORY_SIZE * 2:
-                        chat_history = chat_history[-(MAX_CHAT_HISTORY_SIZE * 2):]
-                    meta["chat"] = chat_history
+                    meta["chat"] = _cap_chat_history(normalize_chat_history(meta.get("chat", [])))

Also applies to: 203-206, 236-236, 370-373, 1576-1576

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rag-service/main.py` around lines 183 - 186, The chat truncation logic is
duplicated in several places, so extract the repeated length check and slice
based on MAX_CHAT_HISTORY_SIZE into a single helper and use it everywhere chat
history is normalized or persisted. Update the related code paths in the
functions handling load, overlay, save, append, and snapshot so they all call
the same helper instead of repeating len(chat_history) > MAX_CHAT_HISTORY_SIZE *
2 and chat_history[-(MAX_CHAT_HISTORY_SIZE * 2):].
rag-service/test_main.py (1)

192-209: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove commented-out test instead of leaving it disabled in code.

This test skeleton is fully commented out and unrelated to the chat-history truncation change in this cohort. Dead/commented-out test code accumulates confusion over time — either finish and enable it (using pytest.mark.skip(reason=...) if intentionally deferred) or drop it from this PR.

♻️ Suggested cleanup
-# def test_extract_pdf_text_worker_enforces_page_limit(tmp_path):
-#     import fitz
-# 
-#     pdf_path = tmp_path / "hello.pdf"
-#     doc = fitz.open()
-#     doc.new_page(width=300, height=144)
-#     doc.save(str(pdf_path))
-#     doc.close()
-# 
-#     # Use a local queue and call the worker directly (no subprocess) to validate limit logic.
-#     q = multiprocessing.Queue(maxsize=1)
-#     _extract_pdf_text_worker(str(pdf_path), max_pages=0, max_chars=1000, out_queue=q)
-#     result = q.get(timeout=2)
-#     assert result["ok"] is False
-#     assert "too many pages" in result["error"].lower()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rag-service/test_main.py` around lines 192 - 209, The commented-out test in
test_main.py should not remain as dead code; either restore it as an active test
or remove it from this PR if it is not part of the chat-history truncation
change. Update the relevant test block around
test_extract_pdf_text_worker_enforces_page_limit so it is either implemented and
runnable, or deleted entirely, and use pytest.mark.skip only if you
intentionally want to keep it deferred.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rag-service/main.py`:
- Line 793: The MAX_CHAT_HISTORY_SIZE setting is used by the chat truncation
logic in main.py, but values of 0 or negative currently bypass or corrupt
trimming instead of being rejected or clamped. Add startup validation where
MAX_CHAT_HISTORY_SIZE is read so the app either enforces a minimum positive
value or falls back to a safe default, and make sure the history-slicing code
paths that rely on this constant use the validated value consistently in the
chat history handling functions.
- Line 793: Move the MAX_CHAT_HISTORY_SIZE assignment so it is defined before
load_sessions() is called at import time, since load_sessions() currently
references that symbol before it exists and can trigger a NameError on startup.
Update the module-level initialization order in main.py so sessions =
load_sessions() happens only after MAX_CHAT_HISTORY_SIZE is set, keeping the
existing load_sessions() and sessions setup intact.

---

Nitpick comments:
In `@rag-service/main.py`:
- Around line 183-186: The chat truncation logic is duplicated in several
places, so extract the repeated length check and slice based on
MAX_CHAT_HISTORY_SIZE into a single helper and use it everywhere chat history is
normalized or persisted. Update the related code paths in the functions handling
load, overlay, save, append, and snapshot so they all call the same helper
instead of repeating len(chat_history) > MAX_CHAT_HISTORY_SIZE * 2 and
chat_history[-(MAX_CHAT_HISTORY_SIZE * 2):].

In `@rag-service/test_main.py`:
- Around line 192-209: The commented-out test in test_main.py should not remain
as dead code; either restore it as an active test or remove it from this PR if
it is not part of the chat-history truncation change. Update the relevant test
block around test_extract_pdf_text_worker_enforces_page_limit so it is either
implemented and runnable, or deleted entirely, and use pytest.mark.skip only if
you intentionally want to keep it deferred.
🪄 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 Plus

Run ID: 702ceb64-bef7-4624-861d-369fa916d785

📥 Commits

Reviewing files that changed from the base of the PR and between e976c8b and 2f13c40.

📒 Files selected for processing (3)
  • .env.example
  • rag-service/main.py
  • rag-service/test_main.py

Comment thread rag-service/main.py
# Configurable session TTL and max cap
SESSION_TTL_MINUTES = int(os.getenv("SESSION_TTL_MINUTES", "43200")) # 30 days default for persistence
MAX_ACTIVE_SESSIONS = int(os.getenv("MAX_ACTIVE_SESSIONS", "1000"))
MAX_CHAT_HISTORY_SIZE = int(os.getenv("MAX_CHAT_HISTORY_SIZE", "100"))

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

MAX_CHAT_HISTORY_SIZE=0 (or negative) silently disables truncation instead of capping history.

Slicing with [-(0*2):] is list[0:] (i.e. the full list), and a negative MAX_CHAT_HISTORY_SIZE produces chat[positive_index:], dropping the wrong elements rather than raising or clamping. If an operator sets MAX_CHAT_HISTORY_SIZE=0 intending to disable/minimize chat retention, this bug means truncation never happens — undermining the OOM protection this PR is meant to add. Validate the value once at startup.

🛡️ Proposed fix
-MAX_CHAT_HISTORY_SIZE = int(os.getenv("MAX_CHAT_HISTORY_SIZE", "100"))
+MAX_CHAT_HISTORY_SIZE = max(1, int(os.getenv("MAX_CHAT_HISTORY_SIZE", "100")))

Also applies to: 184-186, 205-206, 236-236, 371-373, 1576-1576

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rag-service/main.py` at line 793, The MAX_CHAT_HISTORY_SIZE setting is used
by the chat truncation logic in main.py, but values of 0 or negative currently
bypass or corrupt trimming instead of being rejected or clamped. Add startup
validation where MAX_CHAT_HISTORY_SIZE is read so the app either enforces a
minimum positive value or falls back to a safe default, and make sure the
history-slicing code paths that rely on this constant use the validated value
consistently in the chat history handling functions.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find where load_sessions() is invoked at module scope relative to line 793
rg -n 'load_sessions\(\)' rag-service/main.py
rg -n '^MAX_CHAT_HISTORY_SIZE' rag-service/main.py

Repository: FireFistisDead/pdf-qa-bot

Length of output: 294


🏁 Script executed:

sed -n '173,214p' rag-service/main.py
printf '\n---\n'
sed -n '240,260p' rag-service/main.py
printf '\n---\n'
sed -n '785,800p' rag-service/main.py

Repository: FireFistisDead/pdf-qa-bot

Length of output: 4137


Move MAX_CHAT_HISTORY_SIZE above load_sessions() sessions = load_sessions() runs at import time, but load_sessions() reads MAX_CHAT_HISTORY_SIZE before it is assigned, which will raise NameError on startup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rag-service/main.py` at line 793, Move the MAX_CHAT_HISTORY_SIZE assignment
so it is defined before load_sessions() is called at import time, since
load_sessions() currently references that symbol before it exists and can
trigger a NameError on startup. Update the module-level initialization order in
main.py so sessions = load_sessions() happens only after MAX_CHAT_HISTORY_SIZE
is set, keeping the existing load_sessions() and sessions setup intact.

@atul-upadhyay-7 atul-upadhyay-7 closed this by deleting the head repository Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working feature A new feature or improvement fix A targeted fix or cleanup rag-service FastAPI / model service work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Chat history grows without bound — memory exhaustion and OOM crash under sustained usage

2 participants