Repository navigation
Duplicate equivalent external_directory permission requests are emitted for concurrent tool calls #36055
Description
Description
OpenCode can emit multiple equivalent permission.asked events for the same external directory access when concurrent tool calls request access to the same directory.
This is visible in external clients that render each permission.asked event independently. For example, a Telegram bot client receives two separate permission prompts with the same permission, patterns, and always values, but different request IDs.
The built-in TUI does not show the duplicates simultaneously because it renders one queued blocker at a time. However, the duplicate pending requests still exist: with Allow once, the user may be prompted for the same effective permission again; with Allow always, the second matching request may be auto-resolved internally.
Observed behavior
For a single assistant turn, OpenCode may emit two events like:
{
"type": "permission.asked",
"properties": {
"id": "per_...",
"sessionID": "ses_...",
"permission": "external_directory",
"patterns": ["/path/to/sibling/*"],
"always": ["/path/to/sibling/*"],
"tool": {
"messageID": "msg_...",
"callID": "call_read"
}
}
}and:
{
"type": "permission.asked",
"properties": {
"id": "per_...",
"sessionID": "ses_...",
"permission": "external_directory",
"patterns": ["/path/to/sibling/*"],
"always": ["/path/to/sibling/*"],
"tool": {
"messageID": "msg_...",
"callID": "call_glob"
}
}
}The requests are semantically equivalent for directory access, but both remain pending/visible to clients that render permission events independently.
In a Telegram client, the user-visible sequence is:
The user sees two identical permission prompts for the same external_directory access.
The user clicks always on the first prompt.
The user sends another message to continue the session.
The client/session still reports that it cannot continue because another permission needs to be approved.
The user clicks always on the second, identical prompt.
That second approval fails with Permission request not found.
After that, the session can continue normally, but the UX is confusing because the UI first claims a second approval is still required, then says that approval target no longer exists.
Why this happens
Each tool call gets its own permission context in session/tools.ts, including its own tool.callID.
Tools such as read, glob, and grep independently call assertExternalDirectoryEffect(...). When two concurrent tool calls access the same external directory, each tool asks for the same external_directory permission.
Permission.ask stores pending requests by request ID and does not coalesce equivalent pending requests.
If the user replies always to the first request, OpenCode adds an allow rule and automatically resolves the second matching pending request internally. External clients that already rendered the second prompt can still expose it as an apparent blocker, and replying to that stale prompt then fails because the request no longer exists.
Expected behavior
OpenCode should avoid surfacing duplicate equivalent permission requests for the same session and same effective permission scope.
Possible approaches:
- Coalesce equivalent pending permission requests in the permission service.
- Emit only one
permission.askedevent for equivalent pending requests and fan out the reply internally. - Or include grouping/coalescing metadata so external clients can safely render a single prompt and clear stale duplicates.
Notes
This is not specific to a Telegram client. The Telegram client only makes the duplicate events obvious because it renders each permission.asked as a separate message. The underlying duplicate requests originate in OpenCode.
Activity
github-actions commentedon Jul 9, 2026
This issue might be a duplicate of existing issues. Please check:
external_directoryaccess — also describes thePermission request not founderror when responding to a permission prompt, which Duplicate equivalent external_directory permission requests are emitted for concurrent tool calls #36055 explains as a consequence of duplicate coalescing.github-actions commentedon Sep 8, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.