Skip to content

Web/Desktop stale permission prompt returns Permission request not found after interrupted approval #29422

@MingyooLee

Description

@MingyooLee

What happened

Web/Desktop can keep showing permission prompts after the underlying tool/subagent request has already been interrupted or cleaned up. When I approve those prompts later, they all fail with:

Permission request not found

This leaves the session stuck. The original tool/subagent never continues, approving the prompt does not unblock anything, and I cannot continue my work from that session without recovering/restarting/refreshing around the stale state.

This is not tied to a specific command. I saw it for harmless commands and file operations, including date, gh --version, gh search discussions --help, GIT_MASTER=1 git status --short, reading GEMINI.md, and reading/editing packages/opencode/test/permission/next.test.ts.

Expected behavior

If the underlying permission request is no longer pending, Web/Desktop should not keep an approve-able prompt around. It should either remove the prompt when the request is cleaned up, or recover from PermissionNotFoundError by clearing/refetching local permission state.

Confirmed behavior

Local service-level tests reproduce the stale flow directly:

  1. Permission.ask creates a pending request and publishes permission.asked.
  2. The request is cleaned up before approval, either by instance reload/cleanup or by interrupting the ask fiber.
  3. permission.list() is empty after cleanup.
  4. Replying later with the old request ID fails with Permission.NotFoundError.
  5. No permission.replied event is emitted for that stale reply, so a client that still has the prompt has no event-driven removal path.

Focused tests:

reply - stale request after cleanup fails without replied event
reply - stale request after ask interrupt fails without replied event

Verification:

bun test test/permission/next.test.ts -t "stale request"
# 2 pass, 0 fail

bun test test/permission/next.test.ts
# 81 pass, 0 fail

bun typecheck
# passed

bun test test/server/httpapi-instance.test.ts -t "returns typed not found bodies for missing permission"
# 1 pass, 0 fail

Root cause narrowed down

In packages/opencode/src/permission/index.ts, Permission.ask stores the request in the instance-local pending map and removes it in the cleanup/finalizer path. Permission.reply checks pending.get(input.requestID) and returns Permission.NotFoundError before publishing permission.replied if the request is no longer pending.

That service behavior is understandable, but Web/Desktop can still retain the old prompt from the earlier permission.asked event. Once that happens, every later click on the stale prompt is guaranteed to fail with Permission request not found, and the blocked tool/subagent never runs.

Likely fix direction

A fix probably needs one or more of these:

  • emit a cleanup/removal signal when a pending permission is rejected by interruption/finalization
  • make Web/Desktop treat PermissionNotFoundError as a stale local prompt and remove/refetch permissions immediately
  • reconcile Web/Desktop local permission state with permission.list() on reconnect/bootstrap/focus

Related issues

This may share the same stale-client-state class as #28312, but #28312 is the TUI attach flow. This report is specifically Web/Desktop and uses the typed Permission request not found failure after clicking a stale prompt.

#15386 overlaps with stale/non-existent permission IDs, but that issue was about the reply endpoint silently returning 200 true. The current behavior now exposes the stale request as a typed 404; the remaining bug is that Web/Desktop can still keep and offer a prompt whose server-side pending request is already gone.

#26907 and #28651 are related but cover a different case: a child-session prompt remaining after a successful/live reply path. This issue is the earlier/staler case where approval never reaches a live pending request because the underlying ask was interrupted or cleaned up first.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions