pingdotgg / pingdotgg/t3code

[Bug]: Pending approval is never resolved when its provider session dies (the approval half of #5454, not covered by #10586)

Open
#11,799 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

accepted bug via-triage
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:

  1. packages/client-runtime/src/pendingRequests.tsstaleRequestFailureDetails["provider.approval.respond.failed"] holds only stale pending approval request, unknown pending approval request, unknown pending permission request, unknown pending codex approval request. derivePendingRequests therefore never deletes the approval, so the card stays rendered in web, desktop, and mobile.
  2. apps/server/src/orchestration/Layers/ProjectionPipeline.tsisStalePendingApprovalFailureDetail holds three of those fragments, so the projection_pending_approvals row stays pending and pending_approval_count stays above zero.
  3. apps/server/src/orchestration/decider.tsisStaleRequestFailureDetail keeps the request in openRequests, 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
  1. Get a provider to request an approval (command execution, file change, or MCP elicitation) so the approval card renders.
  2. 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.
  3. Press Approve or Deny.
  4. A provider.approval.respond.failed activity 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
  1. Mirror #10586 for approvals (structurally consistent, larger): record a durable approval.resolved dismissal when a response arrives against a missing or stopped session, and dismiss pending approvals on session.exited. This needs an approval analogue of projectionSnapshotQuery.getUserInputActivity, which does not exist yet.
  2. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.