fix: Claude.ai chat export normalizer misses sender/text fields - #243
fix: Claude.ai chat export normalizer misses sender/text fields#243rusel95 wants to merge 7 commits into
Conversation
PR Review: fix: Claude.ai chat export normalizer misses sender/text fieldsExecutive Summary
Affected Areas: Business Impact: Claude.ai privacy export conversations were stored as unparsed JSON strings instead of searchable exchange-pair transcripts. Users who exported from Claude.ai and mined into MemPalace got effectively useless data. Flow Changes: Ratings
PR Health
High Priority Issues(Must fix before merge) 🐛 #1:
|
48db972 to
b307b00
Compare
|
@bgauryy Rebased onto main and fixed the review feedback: Fixed: Null-safe field access — All 18 normalize tests pass. |
|
@bgauryy Pushed additional fix (0aa21c1):
All 19 normalize tests pass. |
|
Normalizer fix with edge case coverage. Here's the normalization flow: flowchart TD
A[Input: conversation file] --> B{Detect format}
B -->|Claude.ai JSON| C[normalize_claude_ai]
B -->|Claude Code JSONL| D[normalize_claude_code]
B -->|Slack JSON| E[normalize_slack]
B -->|Plain text| F[passthrough]
C --> G{Has chat_messages?}
G -->|yes| H[Extract sender + text from each message]
G -->|no| I{Has content array?}
I -->|yes| J[Extract from content blocks]
I -->|no| K[Return empty — null-safe]
H --> L[Format: "sender: text"]
J --> L
L --> M[Join with blank line separators]
M --> N[Return normalized transcript]
18 normalize tests pass including new edge cases for missing sender/text fields. Ready for re-review. |
Claude.ai privacy exports use `sender: "human"/"assistant"` instead of `role: "user"/"assistant"`, and `text` instead of `content`. The normalizer was silently falling through to raw JSON passthrough — conversations were stored as unparsed JSON strings instead of exchange-pair transcripts. Changes: - Check `sender` field before falling back to `role` - Prefer `text` field over `content` (Claude.ai always populates it) - Add conversation boundary markers (--- title ---) for multi-conversation exports so exchange chunking respects conversation boundaries - Add 5 new test cases covering sender field, text field, multi-conversation separation, flat format, and backward compatibility Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
10 additional test cases covering: - Empty chat_messages array (skip, don't crash) - Single-message conversations (no reply → skip) - Content block list fallback when text field is empty - Mixed sender/role fields in same conversation - Unnamed conversations produce no separator header - Long multi-turn conversations preserve order (10 Q&A pairs) - Whitespace-only messages are skipped - ChatGPT conversations.json backward compatibility - Claude Code JSONL backward compatibility - Slack JSON export backward compatibility Total: 18 normalize tests (was 3) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dict.get("text", "") returns None when key exists with null value.
Use (item.get("text") or "") pattern to handle both missing and null
fields safely, preventing AttributeError on .strip().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…misidentification
1. Critical: moved lines.append('') back inside the while loop in
_messages_to_transcript() — without this, multi-turn transcripts
have no separators, breaking downstream chunk_exchanges().
2. Return empty string instead of None when Claude.ai format is
positively identified but all conversations are empty — prevents
fallthrough to Slack parser.
3. Added test_transcript_has_blank_line_separators to catch this
regression.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0aa21c1 to
06436ae
Compare
web3guru888
left a comment
There was a problem hiding this comment.
🔧 Review of #243 — fix: Claude.ai chat export normalizer misses sender/text fields
Scope: +402/−11 · 2 file(s)
mempalace/normalize.py(modified: +28/−11)tests/test_normalize.py(modified: +374/−0)
Strengths
- ✅ Includes test coverage
🟢 Approved — clean, well-structured PR. Good work @rusel95!
🏛️ Reviewed by MemPalace-AGI · Autonomous research system with perfect memory · Showcase: Truth Palace of Atlantis
…mPalace#666) Replace "your memory system" with explicit MemPalace references and tool names (mempalace_diary_write, mempalace_add_drawer, mempalace_kg_add) in stop and precompact hook block reasons. This prevents Claude Code from misinterpreting the hook as a native auto-memory save instruction. Updated in both Python (hooks_cli.py) and standalone shell scripts. Also fix CONTRIBUTING.md Getting Started to show the fork-first workflow, matching the PR Guidelines section.
|
Closing — this is superseded by #685 (merged), which addresses the same Thanks @mvalentsev for landing the fix! |
|
Hey, sorry about that -- I should have spotted your PR before opening mine. You did the real work here: the sender/text fix, the null-safety pass after bgauryy's review, the conversation boundaries, 19 tests. I just didn't look hard enough at existing PRs on normalize.py. My bad. Appreciate you being cool about it. |
Summary
Claude.ai privacy exports use
sender: "human"/"assistant"instead ofrole: "user"/"assistant", andtextinstead ofcontent. The normalizer was silently falling through to raw JSON passthrough — conversations were stored as unparsed JSON strings instead of exchange-pair transcripts.Root cause:
_try_claude_ai_json()only checkeditem.get("role", "")but Claude.ai exports use thesenderfield. Similarly, it readitem.get("content", "")but Claude.ai populates thetextfield.Relates to: #224 (stale drawer retrieval / no sync workflow — correct normalization is a prerequisite for reliable re-mining when sources update)
Changes
senderfield before falling back torole(both in privacy export and flat message paths)textfield overcontent(Claude.ai always populates it;contentmay be a block list)--- title ---) for multi-conversation exports so exchange chunking respects conversation boundaries instead of merging all chats into one blobTest plan
18 normalize tests (was 3) — 6x increase in test coverage for this module:
Core fix tests:
test_claude_ai_sender_field— privacy export withsender: "human"produces transcripttest_claude_ai_text_field_preferred—textfield used even whencontentblock list presenttest_claude_ai_multi_conversation_boundaries— multiple conversations get separator headerstest_claude_ai_flat_sender_format— flat message list withsenderfield workstest_claude_ai_role_field_still_works— backward compatibility withrolefield preservedEdge cases:
test_claude_ai_empty_chat_messages— empty conversations skipped, don't crashtest_claude_ai_single_message_conversation— unanswered messages handledtest_claude_ai_content_block_list_fallback— falls back to content blocks when text is emptytest_claude_ai_mixed_sender_and_role— mixed field names in same conversationtest_claude_ai_unnamed_conversation_no_header— no separator for unnamed conversationstest_claude_ai_long_multi_turn_conversation— 10 Q&A pairs, order preservedtest_claude_ai_whitespace_only_messages_skipped— whitespace-only messages filteredBackward compatibility:
test_chatgpt_conversations_json— ChatGPT mapping tree format still workstest_claude_code_jsonl_still_works— Claude Code JSONL format still workstest_slack_json_still_works— Slack export format still works🤖 Generated with Claude Code