Skip to content

fix(agent): guard refreshSession's query swap like /clear's - #88769

Merged
trunk-io[bot] merged 4 commits into
masterfrom
posthog-code/refresh-query-swap-guard
Aug 25, 2026
Merged

trunk-io[bot] merged 4 commits into
masterfrom
posthog-code/refresh-query-swap-guard

Conversation

@haacked

@haacked haacked commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Problem

A session refresh swaps the SDK query in place, the same way /clear does, but never claimed the lock /clear uses to protect that swap. A prompt, cancel, /clear, or second refresh arriving mid-swap could race the field rewrite and lose a prompt or corrupt the session.

Changes

  • Refresh now shares /clear's swap lock. A prompt arriving mid-swap waits and lands on the fresh input stream, a cancel becomes a no-op, and a second refresh or a /clear gets refused.
  • Any init failure during a refresh, not just a timeout, now closes the session cleanly instead of leaving it half-swapped against a dead query.
  • Mechanical: Session.clearing is renamed to Session.querySwap and both swap paths now go through a shared withQuerySwap helper.

How did you test this code?

New tests cover: a prompt held during a refresh and delivered to the fresh stream, a cancel ignored during a refresh, a second refresh refused, a /clear refused during a refresh, and the session closing when the replacement query's init rejects.

Automatic notifications

  • Publish to changelog?

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Claude Code implemented the fix and tests in this session, then ran /squash to collapse the work into one commit before opening this PR with /create-pr.


Created with PostHog Desktop

refreshSession performed the same in-place SDK query swap as /clear
(retire the query, rewrite session.query/input/queryOptions/
abortController, await init) but claimed none of the Session.clearing
protection, so a prompt, cancel, /clear, or second refresh arriving
mid-swap could race the field rewrite.

Generalize Session.clearing to Session.querySwap, claimed via a
withQuerySwap helper (synchronous claim before the first await,
released in finally), and route both swap bodies through it. Move
retireQuery and the replacement-query construction inside
performRefresh's try block, with the declared-then-assigned
newQuery/newAbortController pattern and a null-guard in the catch, so
a failure anywhere in the swap terminates and closes the session
identically to /clear. Remove the one-line performClear wrapper so
both swap paths read as two hops.

Generated-By: PostHog Desktop
Task-Id: 91ea51d0-4095-4614-acb4-b644deec3612
@trunk-io

trunk-io Bot commented Aug 25, 2026 •

Copy link
Copy Markdown

😎 Merged successfully - details.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 CI report

✅ Trunk lane — non-backend lane

This PR is assigned to the non-backend lane. It does not run backend Python tests and may merge in parallel with PRs in other lanes.

@github-actions

github-actions Bot commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

React Doctor found 1 issue in 1 file · 1 warning.

1 warning

packages/ui/src/features/inbox/components/ReportsInboxView.tsx

Reviewed by React Doctor for commit 61fe232.

@posthog

posthog Bot commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

🦔 ReviewHog reviewed this pull request

Found 0 must fix, 0 should fix, 1 consider.

Published 1 finding (view the review).

Resolved comments: 1 fixed

@hosthog

hosthog Bot commented Aug 25, 2026 •

Copy link
Copy Markdown

HostHog preview — posthog-desktop-web

The previews for this PR have been torn down and no longer serve.

@posthog

posthog Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

ReviewHog Alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ReviewHog Report

Found 1 consider.

@posthog posthog Bot removed the reviewhog label Aug 25, 2026
The /clear refusal reused "A conversation clear is already in progress"
even though querySwap now covers both a refresh and a clear, so a /clear
sent during a refresh told the user a clear was running when none was.
Report the accurate state and point to the next action, matching the
refresh path's symmetric refusal.

Generated-By: PostHog Desktop
Task-Id: 09152925-2a1a-4032-94ba-9d8ab452e5fe
@trunk-io

trunk-io Bot commented Aug 25, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

View Full Report ↗︎ ⋅ Docs

@haacked
haacked marked this pull request as ready for review August 25, 2026 20:23
Copilot AI lite review requested due to automatic review settings August 25, 2026 20:23
@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team August 25, 2026 20:24
@greptile-apps

greptile-apps Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
### Issue 1
products/desktop/packages/agent/src/adapters/claude/claude-agent.ts:578-586
**Prompt registration misses refresh lock**

If a refresh arrives while a prompt is awaiting slash-command refresh or the pre-prompt local-tools check, the prompt has passed this gate but has not entered `turnQueue`, so the refresh is accepted and can retire the input before the prompt dispatches. The message is then pushed into the retiring input and the queued prompt never receives a response.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(agent): correct /clear refusal wordi..." | Re-trigger Greptile

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes a concurrency hazard in the PostHog Desktop Claude ACP adapter where refresh_session could swap the underlying SDK query without taking the same in-place swap lock used by /clear, risking races with prompts/cancel/second swaps.

Changes:

  • Rename Session.clearing to Session.querySwap and treat both /clear and refresh_session as the same “query swap” critical section.
  • Add a shared withQuerySwap helper and route both swap paths through it; refuse cancel and other swaps while a swap is pending.
  • Harden refresh failure handling so any swap failure (including init rejection) terminates the replacement query and marks the session as closed; expand/adjust refresh-focused tests.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
products/desktop/packages/agent/src/adapters/claude/types.ts Renames the session swap lock field and updates its documentation to cover both /clear and refresh swaps.
products/desktop/packages/agent/src/adapters/claude/claude-agent.ts Introduces withQuerySwap, applies it to /clear and refresh, and strengthens error handling to close out failed swaps.
products/desktop/packages/agent/src/adapters/claude/claude-agent.refresh.test.ts Adds concurrency and failure-mode tests for refresh (prompt waiting, cancel ignored, second refresh refused, /clear refused, init rejection closes session).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

The prompt() gate on session.querySwap runs before the pre-enqueue
awaits (slash-command reload, pre-prompt local-tools heal), and the
swap entry points refuse only while turnQueue has an entry. So a
refresh or /clear started during those awaits sees an empty queue,
proceeds, and retires the input stream under the waiting prompt; on
the other side of that await the turn pushes into the queue unpushed
(stranded) or lands on the fresh stream after a /clear. Re-check
querySwap and queryClosed right before turnQueue.push, and reject
before enqueue instead. Mirrors the existing mid-swap prompt test;
adds a regression covering a swap that starts while the prompt is
parked on the local-tools status RPC.

Generated-By: PostHog Desktop
Task-Id: 91ea51d0-4095-4614-acb4-b644deec3612
Generated-By: PostHog Desktop
Task-Id: 91ea51d0-4095-4614-acb4-b644deec3612
@trunk-io
trunk-io Bot merged commit e25723b into master Aug 25, 2026
198 checks passed
@trunk-io
trunk-io Bot deleted the posthog-code/refresh-query-swap-guard branch August 25, 2026 23:48
@deployment-status-posthog

deployment-status-posthog Bot commented Aug 26, 2026 •

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-08-26 00:31 UTC Run
prod-us ✅ Deployed 2026-08-26 00:46 UTC Run
prod-eu ✅ Deployed 2026-08-26 00:45 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants