fix(messaging): get_conversation HTTP 400 on all URNs + inbox/message shaping#1
Open
Dandiggas wants to merge 2 commits into
Open
fix(messaging): get_conversation HTTP 400 on all URNs + inbox/message shaping#1Dandiggas wants to merge 2 commits into
Dandiggas wants to merge 2 commits into
Conversation
encodeURIComponent leaves ( ) unescaped, but msg_conversation URNs embed parentheses — urn:li:msg_conversation:(urn:li:fsd_profile:<id>,<threadId>). The raw parens break LinkedIn's REST-li variables=(...) parser, so get_conversation returned HTTP 400 for every real conversation URN. Encode ( and ) explicitly; add endpoint tests covering round-trip decoding and the absence of raw parens in the built path.
…ages get_inbox: resolve the response's MessagingParticipant entities via *conversationParticipants and emit participants[] (name, headline, profileUrn, profileUrl) with the mailbox owner excluded; fall back to participant names for the title (1:1 threads have title: null) and expose the groupChat flag. Previously threads were unidentifiable without fetching each conversation. get_conversation: messages arrive unordered and unattributed — sort ascending by deliveredAt and resolve *sender/*actor to sender name + senderProfileUrn, plus a fromSelf flag derived from the mailbox-owner id embedded in the conversation URN (no extra /me round-trip). Covered by tests/normalize-messaging.test.ts with synthetic fixtures.
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
get_conversationis currently broken for every real conversation URN (HTTP 400), and the two messaging read tools drop identity/ordering information that is already present in the raw Voyager responses. This PR fixes the URN encoding bug and upgrades the shaping so threads and messages are identifiable from the tool output alone.1. Bug:
get_conversationreturns HTTP 400 for all msg_conversation URNsRoot cause.
conversationMessages()builds the messaging GraphQL path withencodeURIComponent(conversationUrn).encodeURIComponentleaves(and)unescaped, but everymsg_conversationURN embeds parentheses:The raw parens land inside the REST-li
variables=(...)expression, the server-side parser can't parse it, and Voyager answers HTTP 400 — for every conversation, always.Repro (before this patch).
get_inbox→ take anyconversationUrn.get_conversationwith that URN →{"error":"HTTP 400 for /voyagerMessagingGraphQL/graphql?queryId=messengerMessages...","code":"HTTP_ERROR"}.Fix. Percent-encode
(→%28and)→%29explicitly afterencodeURIComponent. Verified live against a real session: before 0/7 calls succeeded (100% HTTP 400) → after 5/5 succeeded, returning the full thread each time.2.
get_inbox: participant identityThe raw response already includes
com.linkedin.messenger.MessagingParticipantentities referenced by each conversation's*conversationParticipants, but the shaper dropped them — and 1:1 threads havetitle: null— so the inbox was a list of anonymous URNs and users had to fetch every conversation to find the one they wanted.shapeInboxnow resolves participants (mailbox owner excluded, derived from the owner id embedded in the conversation URN — no extra/mecall), falls back to participant names for the title, and exposesgroupChat:{ "title": "Ada Lovelace", "participants": [ { "name": "Ada Lovelace", "headline": "Engineer at Example", "profileUrn": "urn:li:fsd_profile:ACoAA...", "profileUrl": "https://www.linkedin.com/in/ACoAA..." } ], "groupChat": false, "lastActivityAt": 1700000300000, "unreadCount": 1, "read": false, "conversationUrn": "urn:li:msg_conversation:(urn:li:fsd_profile:ACoAA...,2-...)" }Verified live: all 20 inbox threads came back with resolved names in a single call.
3.
get_conversation: ordering + sender attributionRaw messages arrive in no guaranteed order and carry
*sender/*actor(MessagingParticipant URNs) that the shaper ignored.shapeConversationMessagesnow sorts ascending bydeliveredAtand attributes each message:{ "text": "Hi — are you free next week?", "deliveredAt": 1700000100000, "sender": "Ada Lovelace", "senderProfileUrn": "urn:li:fsd_profile:ACoAA...", "fromSelf": false }fromSelfis derived from the mailbox-owner id embedded in the message's*conversationURN, avoiding an extra/meround-trip. Verified live: a 20-message thread returned fully sorted and attributed, withfromSelfcorrect in both directions.Tests
tests/endpoints.test.ts: new cases forinboxConversations()andconversationMessages()— asserts no raw parens survive in the built path and that decoding round-trips the original URN.tests/normalize-messaging.test.ts(new): synthetic fixtures mirroring the live messenger GraphQL shape; covers participant resolution + owner exclusion, title fallback, ascending sort, sender attribution,fromSelfin both directions, and graceful degradation when participants are absent.npm run typecheck,npm run lint,npm test(175 passed), andnpm run buildare all green.Notes
get_inbox/get_conversationupdated to match the new output.SingletonLockrecovery, bounded shutdown, read-path retries/timeouts). Those are more opinionated, so I left them out of this PR — happy to follow up with a separate PR if you're interested.🧙 Built with WOZCODE
https://claude.ai/code/session_019KMyYkdjgDzeEAjCkYMP1s