[Bug]: Pending approval is never resolved when its provider session dies (the approval half of #5454, not covered by #10586)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Summary
#5454 covers a pending user-input request becoming permanently stuck once its provider session dies. The same hole exists for pending approvals, and open PR #10586 (which fixes #5454) does not close it: that PR resolves native questions only, on both the respond path and session.exited.
Nothing ever resolves a pending approval when its session goes away.
approval.resolved has exactly one non-test producer, and it is the live provider reporting request.resolved:
// ProviderRuntimeIngestion.ts
case "request.resolved": {
...
kind: "approval.resolved",
On session.exited the ingestion clears turn state and plan progress, and with #10586 also dismisses native user-input requests. Approvals are not touched in any of those paths. So once the provider session is gone, the only remaining route is the user pressing the button, which fails:
// ProviderCommandReactor.ts - processApprovalResponseRequested
const hasSession = thread.session && thread.session.status !== "stopped";
if (!hasSession) {
return yield* appendProviderFailureActivity({
kind: "provider.approval.respond.failed",
detail: "No active provider session is bound to this thread.",
...
});
}
That detail is not in any of the three approval stale-failure matchers, so all three keep the request open:
packages/client-runtime/src/pendingRequests.ts—staleRequestFailureDetails["provider.approval.respond.failed"]holds onlystale pending approval request,unknown pending approval request,unknown pending permission request,unknown pending codex approval request.derivePendingRequeststherefore never deletes the approval, so the card stays rendered in web, desktop, and mobile.apps/server/src/orchestration/Layers/ProjectionPipeline.ts—isStalePendingApprovalFailureDetailholds three of those fragments, so theprojection_pending_approvalsrow stayspendingandpending_approval_countstays above zero.apps/server/src/orchestration/decider.ts—isStaleRequestFailureDetailkeeps the request inopenRequests, so settle is rejected on a thread whose shell flags read as blocked.
The client will reach this branch, because the approval handler has no session-status gate — unlike the main stop button, which is gated on session.status === "running":
// ChatView.tsx - onRespondToApproval
if (!activeThreadId) return;
So every Approve/Deny press appends another provider.approval.respond.failed and the card never closes.
Steps to reproduce
- Get a provider to request an approval (command execution, file change, or MCP elicitation) so the approval card renders.
- While it is pending, make the provider session go away: restart the server, let the session reaper take it, or let the provider child exit.
- Press Approve or Deny.
- A
provider.approval.respond.failedactivity is appended with"No active provider session is bound to this thread.", the card stays open, and the thread keeps a non-zero pending-approval count. Repeat step 3 for as many failure activities as you like.
Expected behavior
Once the provider session is gone the approval can never be accepted, so it should be treated as terminal: the card closes, pending_approval_count drops to zero, and the thread can settle — the same outcome #10586 produces for native questions.
Actual behavior
The approval stays pending indefinitely on both the client and the server projection, every response attempt appends another failure activity, and the thread cannot settle.
Impact
Major degradation or frequent failure
Version or commit
main @ 9d4bb550.
Environment
Found by code inspection while investigating a related stop failure (#11796) on desktop 0.0.40, Windows 11, provider Claude Code.
Logs or stack traces
provider.approval.respond.failed
summary: "Provider approval response failed"
detail: "No active provider session is bound to this thread."
Workaround
None from inside the app for the approval card itself. Restarting the server re-runs reconcileProviderSessions, which settles the orphaned session, but that does not emit approval.resolved, so the pending-approval accounting is not obviously repaired by it either.
Two possible fixes
- Mirror #10586 for approvals (structurally consistent, larger): record a durable
approval.resolveddismissal when a response arrives against a missing or stopped session, and dismiss pending approvals onsession.exited. This needs an approval analogue ofprojectionSnapshotQuery.getUserInputActivity, which does not exist yet. - Add the detail to the three approval matchers (smallest, follows the established pattern): treat
"no active provider session is bound to this thread"as a stale approval failure in the three places listed above. This is what the closed-unmerged #5453 and #4598 did, for user-input as well as approvals.
Happy to open a PR for whichever direction you prefer.
What I verified and what I did not
Verified by reading main @ 9d4bb550: the single approval.resolved producer, the absence of approvals from every session.exited path, the exact contents of all three matcher lists, that #10586's diff touches user-input only, and that onRespondToApproval has no session-status gate.
Not verified: a live runtime repro. This one is a code-path finding, unlike #11796 where I confirmed the failure with a test against unmodified main.
Related
- #5454 and open PR #10586 — the user-input half. #10586 does not cover approvals.
- #11796 — the interrupt/stop path of the same "session died underneath a pending interaction" family.
- #7113 — an OpenCode-adapter-specific leak of pending permission requests, already completed. This report is provider-agnostic and sits in the shared orchestration layer.
- #5453 and #4598 — both closed unmerged; both would have covered the approval detail.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with processApprovalResponseRequested in ProviderCommandReactor.ts, then compare the stale-failure matchers in packages/client-runtime/src/pendingRequests.ts, apps/server/src/orchestration/Layers/ProjectionPipeline.ts, and apps/server/src/orchestration/decider.ts. Confirm the chosen fix handles a missing provider session consistently; done means the approval card closes, pending_approval_count reaches zero, and the thread can settle.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- full-stack
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100