Skip to content

fix(cloud-tasks): re-ask pending question on resume instead of skipping it#3003

Open
MattPua wants to merge 2 commits into
mainfrom
posthog-code/resume-reask-pending-question
Open

fix(cloud-tasks): re-ask pending question on resume instead of skipping it#3003
MattPua wants to merge 2 commits into
mainfrom
posthog-code/resume-reask-pending-question

Conversation

@MattPua

@MattPua MattPua commented Jun 29, 2026

Copy link
Copy Markdown
Member

Problem

When the agent raises an AskUserQuestion choice box on a session a desktop is watching, the user sometimes sees it silently skipped — as if they'd answered when they never did.

Root cause: a relayed, still-pending permission is drained by cleanupSession, which resolved it as a deliberate { outcome: "selected", optionId: "reject" }. That blanket reject is correct for a real shutdown, but _doInitializeSession also calls cleanupSession whenever it rebuilds an existing session — i.e. a seamless resume / reconnect. The reject then gets persisted as the user's choice, so the resumed turn treats the question as answered and skips it.

This is the only path where a connected desktop's choice box gets skipped — the relay-vs-auto-resolve decision (gated on hasDesktopConnected) was already correct, so that behavior is intentionally untouched: a truly headless run (cron/Slack/autonomous) still auto-resolves rather than hanging forever.

Fix

cleanupSession takes a cancelPendingPermissions flag. Only the re-init caller (_doInitializeSession) passes it. When set, pending permissions resolve as { outcome: "cancelled" } — the honest "the user never answered" state (the same outcome the permission handler already uses elsewhere). cancelled unblocks the ACP teardown the same way a reject did (no deadlock), but is not recorded as a choice, so the resumed turn re-asks the question.

stop() and the close command are unchanged — they still reject on genuine shutdown.

Also collapsed two hand-rolled permission-response unions onto the SDK's RequestPermissionResponse type so the shape is expressed once and can't drift.

Tests

  • rejects pending permissions on shutdown cleanup — unchanged shutdown path still records a reject.
  • cancels (does not reject) pending permissions on re-init cleanup — the drain branch resolves as cancelled when the flag is set.
  • re-init cleans up an existing session with permissions cancelled, not rejected — drives the practical trigger (_doInitializeSession with an existing session) and asserts it routes through cleanupSession with cancelPendingPermissions: true, so a wiring regression is caught.

All 124 tests in agent-server.test.ts + question-relay.test.ts pass; tsc and Biome are clean.

Not covered

These are unit tests — they don't stand up the full agent/ACP harness, so there's no single uninterrupted end-to-end "raise question over live SSE → resume → re-asked" run. The downstream assumption that an ACP cancelled outcome causes the resumed turn to re-ask (rather than drop the request) is reasoned from the protocol, not asserted by a test here.

…ng it

When a session is re-initialized while a permission request is still pending
(e.g. a seamless resume or a reconnect that rebuilds the session),
`cleanupSession` drained the pending request by resolving it as a deliberate
`{ outcome: "selected", optionId: "reject" }`. The agent persisted that as the
user's choice, so on resume the question was treated as answered and the choice
box was silently skipped — even though the user was watching and never replied.

Resolve pending permissions as `{ outcome: "cancelled" }` on re-init instead.
The user never answered; the session is just being rebuilt underneath them, so
"cancelled" leaves the request unanswered and the resumed turn re-asks it. Real
shutdown/close paths are unchanged and still record a reject.

Also collapse two hand-rolled permission-response unions onto the SDK's
`RequestPermissionResponse` type (single source of truth).

Tests: shutdown cleanup still rejects; re-init cleanup cancels; and
`_doInitializeSession` (the practical trigger) routes through cleanup with the
cancel flag set.

Generated-By: PostHog Code
Task-Id: 14da7a0f-aa44-4b0d-89b3-52e58f66b1e8
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit 5e13ec1.

@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff (1)

  1. packages/agent/src/server/agent-server.test.ts, line 444-447 (link)

    P2 The cancelPendingPermissions option is missing from the stubSessionCleanup return type, so the second new test is forced to re-cast the entire server to a different inline type just to call cleanupSession({ cancelPendingPermissions: true }). Adding the new option here lets that test drop the extra cast and keeps the stub type in sync with the real signature.

    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!

Reviews (1): Last reviewed commit: "fix(cloud-tasks): re-ask pending questio..." | Re-trigger Greptile

Comment thread packages/agent/src/server/agent-server.test.ts Outdated
Address review feedback and close the coverage gap on the re-init cancel path:

- Parameterize the two twin drain tests (shutdown rejects / re-init cancels)
  into a single it.each, per the repo's preference for parameterized tests.
- Add cancelPendingPermissions to stubSessionCleanup's return type so tests
  call cleanupSession({ cancelPendingPermissions: true }) without re-casting.
- Add an integration test that drives the real ACP requestPermission() entry
  point: the agent's question call blocks while relayed, a re-init tears the
  session down, and the promise the agent is awaiting resolves as "cancelled"
  (not a reject the resumed turn would skip). Documents the one piece a unit
  test can't reach — whether the external agent re-asks — which needs e2e.
- Drop inline comments that just restated the integration test's header.

Generated-By: PostHog Code
Task-Id: 14da7a0f-aa44-4b0d-89b3-52e58f66b1e8
@MattPua MattPua marked this pull request as ready for review June 29, 2026 21:02
@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Reviews (2): Last reviewed commit: "test(cloud-tasks): strengthen pending-qu..." | Re-trigger Greptile

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.

1 participant