Fix shared multi-user conversation reload and AI streaming - #1283
Merged
Paul Lizer (paullizer) merged 2 commits intoAug 18, 2026
Conversation
Sharing a personal conversation left it unusable end to end. Two independent
defects were involved.
selectConversation() called the personal loadMessages() for collaborative
conversations, which fetches /conversation/<id>/messages. That handler only
reads cosmos_conversations_container, but a shared conversation lives in the
collaboration container under a different id than its hidden
source_conversation_id, so the read always 404'd and raised a "Conversation not
found" toast. The call was redundant too: activateConversation() already loads
and renders the shared messages. Two side effects regressed as a result --
chat uploads never reached the Compare/Analyze picker, and task documents from
the previously viewed conversation stayed cached.
Separately, the collaboration stream bridge resolved the internal chat stream
view with current_app.view_functions.get('chat_stream_api'). The Blueprint
security hardening moved chat routes onto a 'backend_chats' Blueprint, so the
endpoint key became 'backend_chats.chat_stream_api'. The lookup returned None
and every collaborative AI request failed with "Chat streaming endpoint is
unavailable" before the model was called. Group collaborative conversations
share the same bridge and were affected as well.
Add a Blueprint-tolerant _resolve_internal_view_function() that keeps the
exact-match fast path for views registered directly on an app, and log a
[COLLABORATION] event when resolution fails so this cannot regress silently.
Drop the personal loader from the collaborative branch and move the search
highlight, task document, and comparison catalog side effects into
loadConversationMessages() so shared conversations keep parity.
Also repair test_collaboration_shared_ai_workflow.py and
test_message_metadata_loading_fix.py, which asserted the pre-Blueprint route
and view-lookup forms, and convert five exact-equality config.py VERSION
assertions to the mandated assert_app_version_at_least() helper -- all were
already failing before this change and are directly in the blast radius of a
version bump.
Fixes #1281
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
Author
|
Copilot resolve the merge conflicts in this pull request |
# Conflicts: # docs/explanation/release_notes.md Co-authored-by: paullizer <34814295+paullizer@users.noreply.github.com>
Contributor
Resolved the |
This was referenced Aug 18, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1281
Sharing a personal conversation left it unusable end to end. Two independent defects were involved.
Root cause 1 — the personal-only messages endpoint was used for shared conversations
selectConversation()called the personalloadMessages()inside its collaborative branch:loadMessages()fetches/conversation/<id>/messages, whose handler reads onlycosmos_conversations_container. A shared conversation lives incosmos_collaboration_conversations_containerunder a different id than its hiddensource_conversation_id, so the read always raisedCosmosResourceNotFoundError→ guaranteed404plus a "Conversation not found" danger toast on every reload and sidebar click.The call was also redundant —
activateConversation()→loadConversationMessages()already clears#chatboxand renders the shared messages. Two side effects regressed because of it:updateComparisonChatUploadCatalog([])ran from thecatch, so chat uploads in shared conversations never reached the Compare/Analyze picker.updateConversationTaskDocumentsFromMessages()never ran, so task documents from the previously viewed conversation stayed cached against the wrong conversation.Root cause 2 — the Blueprint migration broke the internal stream endpoint lookup
Commit
094424bc("Harden route blueprint security policies") moved every route module onto Blueprints.app.pyregisters chat routes viaregister_route_blueprint('backend_chats', ...), so/api/chat/streamis declared with@bp.route(...)and Flask stores it underbackend_chats.chat_stream_api. The unqualified lookup returnedNone, so every collaborative AI request failed withStream interrupted: Chat streaming endpoint is unavailablebefore the model was ever called. Group collaborative conversations share the same bridge and were broken too.current_app.view_functionswas the only remaining Blueprint-prefix assumption in the app — everyurl_for()call was already dotted.Changes (v0.250.224)
route_backend_collaboration.py_resolve_internal_view_function()(exact-match fast path first, then last-segment match) and a[COLLABORATION]log event when resolution fails.chat-conversations.jsloadMessages()call from the collaborative branch.chat-collaboration.jsloadConversationMessages()now clears stale search highlights, rehydrates task documents, refreshes the comparison chat upload catalog, and reapplies a pending highlight.chat-messages.jsupdateComparisonChatUploadCatalogfor reuse.Follow-up hardening (v0.250.225)
Three further defects surfaced while tracing this bug. None caused the reported symptoms, so they were split out rather than mixed into the fix above.
Shared stream errors now always carry
conversation_kindchat-streaming.jspicks its post-error recovery endpoint fromconversation_kind, falling back to the personal endpoint when absent. None of the seven_serialize_stream_error()call sites in the bridge set it — so a shared conversation would have hit the exact 404 this PR removes.It could not fire in practice: the surrounding guard also requires
message_id, and those error payloads never carried one. A latent trap rather than a live defect — but one that returns the moment anyone addsmessage_idto an error payload.Rather than repeating the field seven times, all failures now funnel through one nested helper that cannot omit it.
test_collaboration_stream_errors_always_carry_conversation_kindwalks the AST ofstream_collaboration_message_apiand asserts exactly one raw_serialize_stream_errorcall remains, that it setsconversation_kind=COLLABORATION_KIND, and that every failure path routes through the helper — so a new error path without the tag now fails CI.Repaired 59 stale
@app.routeassertions across 32 test filesProduction has exactly one
@app.routeleft — an example inside aswagger_wrapper.pydocstring. The test suite still asserted the old form in 82 places across 40 files.This is how root cause 2 shipped.
test_collaboration_shared_ai_workflow.pyexisted specifically to guard this bridge, but broke on line 35 (an@app.routeassertion) and died before reaching line 37, which checked the endpoint lookup. The test that should have caught the bug was standing right there, red for an unrelated reason.Each rewrite was verified against a real
@bp.routepath inapplication/single_appbefore being changed. 14 occurrences were deliberately left alone because no matching production route exists — those point at routes that appear to have been removed or renamed, which is a separate problem that must not be papered over with a passing assertion.Measured on the affected files: 47 failures → 34, zero newly broken.
Dead post-stream reload guard — filed as #1286, not fixed here
chat-streaming.js:1449and:1515guard ontypeof window.chatMessages?.loadMessages === 'function', butloadMessagesis not among the six functions assigned towindow.chatMessages, andgit log -Sconfirms it never was. Dead since commit54e37c87.The backend sets
reload_messages: truewhen an agent plugin persists extra message documents into Cosmos, so those stay invisible until a manual reload. Impact is probably narrow — the final payload rendersimage_urlseparately — but sizing it needs a repro, and switching on a path that has never executed in production is not a safe blind change.Tests
New
functional_tests/test_collaboration_multi_user_reload_and_stream_fix.py(7 checks) — builds a Flask app registeringchat_stream_apion a Blueprint namedbackend_chatsexactly like production and asserts the resolver finds it even thoughchat_stream_apiis not aview_functionskey; asserts the unprefixed case still resolves and unknown names returnNone; asserts the frontend wiring and the error-tagging contract. The resolver is loaded by compiling just its AST node, so real behavior is exercised without standing up Cosmos/Search/OpenAI.New
ui_tests/test_chat_collaboration_conversation_load.py— extracts the shippedloadConversationMessages()source, runs it in Chromium, and asserts/conversation/<id>/messagesis never requested, the collaboration endpoint is called exactly once, all four side effects fire, and no console errors occur.Both new tests were verified to fail against pre-fix source and pass after.
Other test repairs (all already failing before this PR)
test_collaboration_shared_ai_workflow.pyandtest_message_metadata_loading_fix.pyasserted pre-Blueprint@app.route/view_functions.get(...)forms.config.pyVERSION with exact equality, which.github/instructionsexplicitly forbids and which any version bump breaks. Converted toassert_app_version_at_least().Validation
test_collaboration_shared_ai_workflow.pytest_chat_layered_message_masking.pyui_tests/suiteZero newly broken tests in any suite. Every remaining failure was confirmed identical against stashed pre-change source (Azure Cosmos credentials required at import, or unrelated stale assertions). All checks re-run after the Development merge at
194e3f75.