Skip to content

fix(sessions): stale-conversation cost warning is a blocking prompt-input state#3152

Merged
charlesvien merged 7 commits into
mainfrom
posthog-code/stale-conversation-gate-overlay
Jul 6, 2026
Merged

fix(sessions): stale-conversation cost warning is a blocking prompt-input state#3152
charlesvien merged 7 commits into
mainfrom
posthog-code/stale-conversation-gate-overlay

Conversation

@pauldambra

@pauldambra pauldambra commented Jul 5, 2026

Copy link
Copy Markdown
Member

Problem

The staff-only warning for continuing a large, idle conversation (#3122) was an AlertDialog that flashed up and immediately disappeared.

Root cause: the gate was purely reactive on "newest event ts older than 60 min". Opening a stale session reconnects the agent, which immediately re-emits notifications (usage update, SDK handshake) that the workspace-server stamps with ts: Date.now() — so the conversation instantly looked active again and the dialog closed itself before it could be read.

Change

  • Latch the gate in the view-state store: once it engages for a session (recording the last-activity time observed at that moment), only an explicit user choice releases it. Freshly-stamped reconnect events can no longer dismiss it.
  • No more modal. The warning renders as a state of the prompt input — the composer slot swaps to the same ActionSelector used by permission prompts, so you must choose before continuing:
    1. Compact and continue — sends /compact; pays the reload once, every later turn is cheaper
    2. Continue anyway — full-price reload, keeps the whole conversation
    3. Start a new session — avoids the cost entirely
  • It takes precedence over a pending permission prompt, since answering one also resumes the costly turn. The thread stays visible and scrollable above, so you can review the conversation while deciding.
  • ActionSelector's footer now only advertises "Esc to cancel" when onCancel is actually wired (this selector has no cancel; permission prompts are unaffected).
  • Adds a Storybook story: Sessions/StaleConversationCostNotice (thread backdrop capped at the real CHAT_CONTENT_MAX_WIDTH, same 750px wrapper the composer uses — the notice renders at exactly the prompt input's width).

Testing

  • Store latch behaviour covered in staleConversationGateStore.test.ts (engage snapshots, idempotence, acknowledged sessions can't re-engage)
  • pnpm --filter @posthog/ui typecheck, Biome clean
  • Visual check via the Storybook story in light and dark themes

Created with PostHog Code

@trunk-io

trunk-io Bot commented Jul 5, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

Copy link
Copy Markdown
Member Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@pauldambra pauldambra changed the title fix(sessions): make stale-conversation cost warning a blocking overlay fix(sessions): stale-conversation cost warning is a blocking prompt-input state Jul 5, 2026
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit 0a8bdca.

@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "fix(sessions): cap the story's fake thre..." | Re-trigger Greptile

Comment thread packages/ui/src/features/sessions/components/SessionView.tsx Outdated
@pauldambra pauldambra added the Stamphog This will request an autostamp by stamphog on small changes label Jul 5, 2026

@pauldambra pauldambra left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

🤖 Automated comment by QA Swarm — not written by a human

QA Swarm review complete. See inline comments.

Comment thread packages/ui/src/features/sessions/components/SessionView.tsx
Comment thread packages/ui/src/features/sessions/components/SessionView.tsx Outdated
Comment thread packages/ui/src/features/sessions/components/SessionView.tsx
Comment thread packages/ui/src/features/sessions/components/SessionView.tsx
Comment thread packages/ui/src/features/sessions/useStaleConversationGate.ts Outdated
Comment thread packages/ui/src/features/sessions/useStaleConversationGate.ts
Comment thread packages/ui/src/primitives/action-selector/ActionSelector.tsx
Comment thread packages/ui/src/features/sessions/components/StaleConversationCostNotice.tsx Outdated
Comment thread packages/ui/src/features/sessions/components/StaleConversationCostNotice.tsx Outdated
@pauldambra

pauldambra commented Jul 5, 2026

Copy link
Copy Markdown
Member Author

Note

🤖 Automated comment by QA Swarm — not written by a human

Multi-perspective review: qa-team (specialists + generalists), paul-reviewer, xp-reviewer, security-audit

Verdict: 💬 APPROVE WITH NITS (round 2 @ 92a819f)

The round-1 fixes are faithfully implemented with no regressions; the delta's new dynamism opens two narrow edge cases worth closing. Nothing blocks.

Key findings

  • 🟡 MEDIUM — the conditional compact option gives ActionSelector its first mutable options list; selectedIndex is never clamped, so a mid-decision permission arrival can make Enter throw or fire the wrong choice (StaleConversationCostNotice.tsx:49)
  • 🟢 LOWstaleGate.active && !isRunning falls through to an answerable PermissionSelector during "connecting" windows where pendingPermissions isn't cleared, briefly uncovering the action the gate exists to block (SessionView.tsx:609)
  • ⚪ NITs — derive the choice union from StaleConversationGateChoiceProperties["choice"]; useMemo vs useCallback inconsistency in the three gate handlers; no story for the two-option (permission-pending) layout; copy preference on the spend parenthetical placement

Convergence

None this round — each finding came from a single reviewer.

Reviewer summaries

Reviewer Assessment
🔍 qa-team Round-1 fixes verified (typecheck + store tests pass); two edge cases from the new conditional option and isRunning gating are worth closing.
👤 paul Every round-1 finding got a clean fix; loves the analytics wiring; only a copy nit on where "spent so far" landed.
📐 xp Small, well-motivated behaviour changes with load-bearing comments; two NITs about pre-existing duplication shapes the delta merely follows.
🛡 security-audit No findings: the new event carries only a closed enum and two numeric aggregates, staff-only, via the existing typed sink.
Previous rounds (1)

round 1 @ f05d8c5 — 💬 APPROVE WITH NITS: 3 MEDIUM (compact/permission double reload, ack-before-send, mid-reconnect actionability), 5 LOW, 2 NIT; all fixed in 92a819fe2 or resolved as intentional.


Automated by QA Swarm — not a human review

@pauldambra pauldambra marked this pull request as ready for review July 5, 2026 20:08
@pauldambra pauldambra added the Create Release This will trigger a new release label Jul 5, 2026

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR exceeded the automated size gate (504 lines vs 500-line ceiling). The changes themselves appear well-reasoned — all 22 inline review comments are resolved, including substantive issues around permission prompt ordering, offline guards, and Escape key swallowing. Request a human reviewer to confirm the fix is complete before merging.

@stamphog stamphog Bot removed the Stamphog This will request an autostamp by stamphog on small changes label Jul 5, 2026
@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Reviews (2): Last reviewed commit: "fix: address qa-swarm review on the stal..." | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 92a819fe2d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/ui/src/features/sessions/components/SessionView.tsx

@pauldambra pauldambra left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

🤖 Automated comment by QA Swarm — not written by a human

QA Swarm round 2 complete. See inline comments.

Comment thread packages/ui/src/features/sessions/components/SessionView.tsx Outdated
Comment thread packages/ui/src/features/sessions/components/SessionView.tsx Outdated
Comment thread packages/ui/src/features/sessions/components/SessionView.tsx Outdated
@pauldambra pauldambra added the Stamphog This will request an autostamp by stamphog on small changes label Jul 5, 2026

@stamphog stamphog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The automated size gate denied this PR (541 lines vs. 500-line ceiling). The changes are well-reasoned and all 36 inline comments are resolved, but a human reviewer should confirm the fix is complete before merging given the gate denial.

@stamphog stamphog Bot removed the Stamphog This will request an autostamp by stamphog on small changes label Jul 5, 2026
@pauldambra pauldambra requested a review from webjunkie July 5, 2026 21:03
The staff-only warning for continuing a large, idle conversation was an
AlertDialog that flashed and immediately vanished: opening a stale
session reconnects the agent, which re-emits usage/handshake
notifications stamped ts=Date.now() in the workspace-server, so the
reactive staleness check flipped back off before the user could read
the warning.

Latch the gate in the view-state store (with the last-activity time
observed at engagement) so only an explicit choice releases it, and
replace the modal with an overlay covering the chat window - thread,
permission prompt, and composer - so the user must choose "Continue
anyway" or "Start a new session" before continuing.

Generated-By: PostHog Code
Task-Id: 1ec8e82e-b126-48b8-8fd4-54edcaf041d5
"Compact and continue" (the recommended, solid action) acknowledges the
gate and sends /compact: the reload is paid once, then every later turn
runs on the summarized, smaller thread. "Continue anyway" and "Start a
new session" remain as the full-context and zero-cost choices.

Generated-By: PostHog Code
Task-Id: 1ec8e82e-b126-48b8-8fd4-54edcaf041d5
Drop the floating overlay: when the gate engages, the composer slot
renders an ActionSelector (the same primitive as permission prompts)
with the three choices - compact and continue, continue anyway, start
a new session. It takes precedence over a pending permission, since
answering one also resumes the costly turn. Adds a Storybook story,
and the ActionSelector footer only advertises Esc when onCancel is
actually wired.

Generated-By: PostHog Code
Task-Id: 1ec8e82e-b126-48b8-8fd4-54edcaf041d5
The full-width backdrop rows made the notice look narrower than the
prompt input; in SessionView both share the same mx-auto 750px wrapper.

Generated-By: PostHog Code
Task-Id: 1ec8e82e-b126-48b8-8fd4-54edcaf041d5
- suppress "Compact and continue" while a permission is pending (a
  queued /compact would land after the permission resumes the costly
  turn, paying the reload twice)
- guard the compact choice on connectivity so the gate is not released
  when the send cannot happen
- wait for isRunning before showing the notice so choices cannot fire
  into a session that is still reconnecting
- track which of the three options staff pick (typed analytics event)
- let Escape bubble out of ActionSelector when there is no onCancel
- explicit new-session branch in the notice dispatch, cost parenthetical
  moved next to the pricing sentence, comment on the active derivation

Generated-By: PostHog Code
Task-Id: 1ec8e82e-b126-48b8-8fd4-54edcaf041d5
- remount the notice when the option set changes so ActionSelector's
  highlighted index can't dereference past the list or fire a different
  option than the one shown
- keep the gate covering the composer slot while reconnecting (handoff
  can leave pendingPermissions set; the connecting state shows instead
  of an answerable permission prompt)
- derive the choice union from StaleConversationGateChoiceProperties
- handleStaleNewSession is a useCallback like its siblings, with the
  undefined guard at the call site
- story for the two-option permission-pending layout

Generated-By: PostHog Code
Task-Id: 1ec8e82e-b126-48b8-8fd4-54edcaf041d5
- clamp selectedIndex in useActionSelectorState when options change,
  replacing the consumer-level remount key (fixes the primitive for
  every consumer with live-changing options)
- extract ComposerSlot/ComposerWidth and ConnectingToAgent in
  SessionView, collapsing three copies of the composer-slot wrapper and
  two copies of the connecting spinner
- reuse formatUsd instead of hand-rolled toFixed(2)
- plain functions instead of useCallbacks for the gate handlers (the
  notice isn't memoized and the closed-over usage values change per
  streamed event anyway); single guard on onNewSession
- flatten the gate store's engagement Map to number | null and keep the
  Map reference stable when acknowledge has nothing to release

Generated-By: PostHog Code
Task-Id: 1ec8e82e-b126-48b8-8fd4-54edcaf041d5
@charlesvien charlesvien force-pushed the posthog-code/stale-conversation-gate-overlay branch from c404d6b to 0a8bdca Compare July 6, 2026 00:27
@charlesvien charlesvien enabled auto-merge (squash) July 6, 2026 00:28
@charlesvien charlesvien merged commit 542b9a9 into main Jul 6, 2026
24 checks passed
@charlesvien charlesvien deleted the posthog-code/stale-conversation-gate-overlay branch July 6, 2026 00:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Create Release This will trigger a new release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants