fix(channels): dedupe cloud channel context echo so prompt shows once#2806
Conversation
In cloud mode the channel's CONTEXT.md *is* delivered to the agent (folded into pendingUserMessage at task creation), but the conversation rendered the user's prompt twice: first the optimistic placeholder (seeded from the bare task description, no context), then the echoed session/prompt that carries the appended <channel_context> block. mergeConversationItems deduped optimistic↔echo by exact content equality, so the context-bearing echo never matched its placeholder and both rendered — reading as "context shows up a second time / wasn't included the first time". Local sessions don't hit this (they swap optimistic→real by id). Compare on the channel-context-stripped text instead, and upgrade the pinned bubble to the richer echoed copy so the CONTEXT.md chip renders in place, without a duplicate. Generated-By: PostHog Code Task-Id: e2321274-b00c-4451-8411-a80a515a8965
|
React Doctor found no issues in the changed files. 🎉 Reviewed by React Doctor for commit |
Prompt To Fix All With AIFix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
packages/ui/src/features/sessions/components/mergeConversationItems.test.ts:68-71
The expression `pinned?.type === "user_message" && pinned.content` is used as the actual value passed to `.toBe(...)`. When the assertion fails (e.g. `pinned` is `undefined` or the content doesn't match), Vitest reports the received value as `false` rather than the actual content string or `undefined`, making the failure message cryptic. Splitting into two separate assertions gives a precise message for each failure condition.
```suggestion
const pinned = result.find((i) => i.id === "opt");
expect(pinned?.type).toBe("user_message");
expect((pinned as Extract<(typeof result)[number], { type: "user_message" }>).content).toBe(
echoedWithContext,
);
```
Reviews (1): Last reviewed commit: "fix(channels): dedupe cloud channel cont..." | Re-trigger Greptile |
| const pinned = result.find((i) => i.id === "opt"); | ||
| expect(pinned?.type === "user_message" && pinned.content).toBe( | ||
| echoedWithContext, | ||
| ); |
There was a problem hiding this comment.
The expression
pinned?.type === "user_message" && pinned.content is used as the actual value passed to .toBe(...). When the assertion fails (e.g. pinned is undefined or the content doesn't match), Vitest reports the received value as false rather than the actual content string or undefined, making the failure message cryptic. Splitting into two separate assertions gives a precise message for each failure condition.
| const pinned = result.find((i) => i.id === "opt"); | |
| expect(pinned?.type === "user_message" && pinned.content).toBe( | |
| echoedWithContext, | |
| ); | |
| const pinned = result.find((i) => i.id === "opt"); | |
| expect(pinned?.type).toBe("user_message"); | |
| expect((pinned as Extract<(typeof result)[number], { type: "user_message" }>).content).toBe( | |
| echoedWithContext, | |
| ); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/ui/src/features/sessions/components/mergeConversationItems.test.ts
Line: 68-71
Comment:
The expression `pinned?.type === "user_message" && pinned.content` is used as the actual value passed to `.toBe(...)`. When the assertion fails (e.g. `pinned` is `undefined` or the content doesn't match), Vitest reports the received value as `false` rather than the actual content string or `undefined`, making the failure message cryptic. Splitting into two separate assertions gives a precise message for each failure condition.
```suggestion
const pinned = result.find((i) => i.id === "opt");
expect(pinned?.type).toBe("user_message");
expect((pinned as Extract<(typeof result)[number], { type: "user_message" }>).content).toBe(
echoedWithContext,
);
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
…ows immediately The cloud sandbox takes seconds to boot and echo the prompt back, so the optimistic placeholder was seeded from the bare task description and showed no CONTEXT.md chip until the echo landed. Task creation now hands the context-bearing pendingUserMessage to the session service (rememberInitialCloudPrompt), which seeds the placeholder with it — so the chip renders the instant the user submits, with the <channel_context> block stripped to a chip by UserMessage as usual (never raw XML). Best-effort and in-memory: lost on a reload during the boot window, where the merge-layer dedupe still renders the echo correctly (chip, no duplicate). Generated-By: PostHog Code Task-Id: e2321274-b00c-4451-8411-a80a515a8965
Addresses Greptile review: the combined `pinned?.type === "user_message" && pinned.content` expression reported `false` on failure instead of the actual content. Assert the type, narrow, then assert the content separately. Generated-By: PostHog Code Task-Id: e2321274-b00c-4451-8411-a80a515a8965
|
Reviews (2): Last reviewed commit: "test(channels): split combined assertion..." | Re-trigger Greptile |
Problem
In cloud mode, a channel task's prompt appeared to be missing its
CONTEXT.md— and then the prompt showed up again, this time with the context. Local tasks were fine.The context was actually being delivered to the agent the whole time (the saga folds the channel CONTEXT.md into
pendingUserMessageat task creation). The visible bug was a duplicated user message in the conversation:sessionService.hydrateCloudTaskSessionFromLogs), with no channel context.session/promptthat streams back from the sandbox carries the appended<channel_context>…</channel_context>block.mergeConversationItemsdeduped optimistic↔echo by exactcontentequality, so the context-bearing echo never matched its placeholder → both rendered.Local sessions don't hit this — they swap optimistic→real by id, not by content.
Changes
mergeConversationItemsnow compares on the channel-context-stripped text (reusingextractChannelContext), so the echo dedupes against its placeholder.CONTEXT.mdchip renders in place — once — instead of as a second message.Why: the channel context plumbing worked end-to-end, but cloud users saw the prompt twice (the second copy holding the context), which read as "context wasn't included." This makes the cloud conversation match local.
How did you test this?
pnpm --filter @posthog/ui test src/features/sessions/components— 86 passed (incl. newmergeConversationItemscase +channelContext/UserMessage).pnpm --filter @posthog/ui typecheck— clean.biome checkon the changed files — clean.Automatic notifications
Created with PostHog Code