Fix deleted chat messages reappearing as masked - #1653
Merged
Paul Lizer (paullizer) merged 6 commits intoOct 6, 2026
Merged
Paul Lizer (paullizer) merged 6 commits into
Paul Lizer (paullizer) merged 6 commits into
Conversation
With conversation archiving enabled, deleting a chat message keeps its document with metadata.is_deleted set and masks it only as a fail-safe. Most readers never checked is_deleted, so the V2 chat rendered deleted messages as "This message is masked", and the same documents came back through attempt switching, retry and edit, forks, convert-to-shared, search, MCP reads, the conversation summary, and the history sent to the model. The mask routes could also clear the fail-safe. - Add functions_message_deletion with one shared predicate and filter. - Exclude soft-deleted messages from the message list, search, summaries, model history, fork and shared-conversation copies, shared listings, and MCP reads. - Return 404 for soft-deleted targets of delete, retry, edit, switch-attempt and both mask routes; retry and edit no longer inherit deletion from attempt 1. - Promote another attempt only when a delete removes the active attempt's question, never a deleted attempt, and leave a single active attempt. - V2: drop deleted messages on load and re-read; count attempts by position. - Bump version to 0.261.253; add fix documentation, functional and UI tests. Fixes #1649. Regular-mask gaps found while fixing this are tracked in #1650. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Refs #1649. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…masked Brings in the five commits from #1637 and #1652. Conflicts resolved: - config.py: the base moved to 0.261.255, so this fix takes 0.261.256. - release_notes.md: the base's v0.261.253 section stays as it is, and this fix's entries move to a new v0.261.256 section at the top. The new shared-conversation generated-documents routes read messages through list_collaboration_messages, which already leaves out soft-deleted copies. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The base branch reached 0.261.255 and already uses 0.261.253 for another fix, so this fix's test headers, minimum-version check, and fix documentation now name 0.261.256, matching config.py and its release notes section. Refs #1649. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…masked Brings in #1654, which renumbered its progress indicator fix to 0.261.256. - config.py: both sides had set 0.261.256, so git merged it silently; this fix now takes 0.261.257 so the two fixes keep separate versions. - release_notes.md: the base's v0.261.256 section stays as it is, and this fix's entries move to a new v0.261.257 section at the top. - chatStore.ts merged cleanly: the base's orchestrationSurface phase and this branch's deleted-message filter touch different code. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Paul Lizer (paullizer)
merged commit Oct 6, 2026
a695132
into
paullizer-react-v2-ui
10 checks passed
Paul Lizer (paullizer)
added a commit
that referenced
this pull request
Oct 6, 2026
15 of 24 tasks
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.
Summary
metadata.is_deletedset and is also masked as a fail-safe./api/get_messagesstill returned it, and V2 didn't filter it out (the classic chat does). So it reappeared as a masked placeholder in the re-read after the delete and every time the conversation was reopened. With archiving off the delete is permanent, which is why it only happened sometimes.functions_message_deletion.py) is applied at every read, copy, search, and model boundary. Delete, retry, edit, switch-attempt, and both mask routes return 404 for a deleted target. V2 also drops deleted messages on load and re-read. Stored documents and archive copies are unchanged. Shared conversations that already hold copies hide them at read time, so no migration is needed.Worth a careful look:
delete_messagemoved into_promote_remaining_thread_attempt. It uses a parameterized query instead of the string-builtNOT IN (...)query, and deactivates whatever is left of the deleted attempt.switch_attemptnow selectsc.metadatainstead ofSELECT DISTINCT ... thread_attemptso it can seeis_deleted. Retry's source lookup no longer usesTOP 1, so deleted attempts can be skipped.TOP(recent assistant replies, diagram-edit grounding) filter in the Cosmos query withNOT_SOFT_DELETED_COSMOS_FILTER, soTOPcounts only messages that still exist.soft_deleted_message_policy_version, so cached results that contain deleted snippets aren't served.test_analyze_backend_saved_integration.py,test_content_screening_history.py, andtest_group_collaboration_source_storage_fix.py. They now bindexclude_soft_deleted_messages.Linked issue
Fixes #1649
Refs #1650 (gaps in regular masking found while fixing this, left for a follow-up)
Release Notes & Latest Features
Is this visible to end users?
Is this admin-facing (Admin Settings, governance, deployment, config)?
Should this become a Latest Feature card?
Screenshot needed for the card?
Version bump
application/single_app/config.pyVERSIONthird segment bumped, or not needed because this is docs-only (0.261.252 to 0.261.253)deployers/version.txtbumped, or not needed becausedeployers/was not changed (not changed)Testing / validation
New tests:
python -m pytest functional_tests\test_chat_soft_deleted_message_visibility_fix.py -q -p no:cacheprovider: 2 passed. The test imports the real modules in a fresh process with network access blocked and runs 22 scenarios (the helper plus 21 paths) under normal and optimized Python.python functional_tests\test_chat_soft_deleted_message_visibility_fix.pyalso reports 2/2 passed.python -m pytest .\ui_tests\test_v2_chat_deleted_messages.py -q -p no:cacheprovider: 2 passed. It runs the production message list, composer, and chat store in Chromium. Its HTTP stub returns deleted messages the way the server did before this fix.Builds and existing tests:
npm --prefix application\v2_ui run typecheck: passednpm --prefix application\v2_ui run build -- --outDir ..\..\ui_tests\artifacts\orchestration-plan-editor: passedpython functional_tests\test_v2_message_actions.py: 7/7 passedpython functional_tests\test_v2_chat_phase1_fixes.py: 10/10 passedpython functional_tests\test_v2_message_masking.py: 9/9 passedpython functional_tests\route_tests\test_route_blueprint_policy_inventory.py: 12/12 passedpython functional_tests\route_tests\test_route_unauthenticated_policy_contract.py: 7/7 passedpython functional_tests\route_tests\test_route_policy_test_coverage.py: 2/2 passedpython functional_tests\test_docs_app_surface_coverage.py: 7/7 passedpython functional_tests\test_docs_site_quality.py: 6/6 passedpython -m pytest <39 related functional test files> -q -p no:cacheprovider --continue-on-collection-errors -W ignore::pytest.PytestReturnNotNoneWarning -rfE(files listed below): 415 passed, 44 failed, 204 errors. I compared failing test IDs against a run of the same files on unmodified HEAD, and no test fails only with this change. The ones I traced need Azure configuration that this environment doesn't have; for example, importingconfig.pybuilds a Cosmos client.Failures that also occur on unmodified HEAD:
python scripts\check_broken_access_control.py <the 8 changed application .py files>: 2 findings, both on lines this PR doesn't change. HEAD reports the same two.python functional_tests\test_docs_release_notes_integrity.py: the generated release-notes pages are already stale on HEAD, by more than 158 versions. I didn't regenerate them here, to keep the diff focused.python functional_tests\test_docs_link_integrity.py: 11 broken relative links elsewhere in the docs.Environment note: the tests that import route modules ran in a fresh virtualenv with
application/single_app/requirements.txtinstalled. This machine's global Python has a pyOpenSSL/cryptography mismatch that breaks importingroute_backend_chats.The 39 related functional test files
test_advanced_conversation_search_matching_fix.py,test_analyze_backend_saved_integration.py,test_assist_thread_submission_id.py,test_backend_conversations_swagger_integration.py,test_chat_cited_source_tracking.py,test_chat_layered_message_masking.py,test_chat_soft_deleted_message_visibility_fix.py,test_chat_stream_history_context_fix.py,test_collaboration_conversation_summary_export_fix.py,test_collaboration_message_delete_fix.py,test_collaboration_shared_conversation_management_fix.py,test_content_screening_history.py,test_conversation_export.py,test_conversation_fork.py,test_conversations_read_ownership_authorization.py,test_enhanced_citations_blob_and_collaboration_fix.py,test_group_collaboration_source_storage_fix.py,test_image_chat_route_integration.py,test_inbound_mcp_governance_and_tools.py,test_inbound_mcp_server_shell.py,test_m365_collaboration_action_cards.py,test_m365_conversation_lifecycle.py,test_message_block_revisions.py,test_orchestration_conversation_context.py,test_orchestration_conversation_context_routes.py,test_orchestration_deliverables.py,test_orchestration_workflow_run_adapter.py,test_personal_conversation_followup_authorization.py,test_stored_xss_share_activity_and_masking_fix.py,test_v2_chat_phase1_fixes.py,test_v2_message_actions.py,test_v2_message_masking.py,test_v2_prompt_attachment_persistence.py,test_v2_shared_conversations.py,test_workflow_chat_delivery_placement_and_masking.py,test_workflow_chat_delivery_refusals.py,test_workflow_result_chat_routes.py,test_workflow_result_orchestration_lineage.py,test_workflow_result_review_paths.pyDocumentation
docs/explanation/release_notes.md)docs/explanation/fixes/SOFT_DELETED_MESSAGES_SHOWN_AS_MASKED_FIX.md, listed in the fixes index. The Enable Conversation Archiving row indocs/admin/data-lifecycle.mdnow says deleted messages are archived and hidden.)Security checklist
@swagger_route(security=get_auth_security())(no new routes)sanitize_settings_for_user()(no settings changes)