Skip to content

ACP: premature tool_call_update with in_progress clobbers pending session/request_permission dialog #14301

Closed

Description

@olehpalianytsiagr

Description

When OpenCode sends a session/request_permission request to an ACP client, it simultaneously (in the same message batch) sends a tool_call_update notification transitioning the same tool call to status: "in_progress". This happens before the client has responded to the permission request.

Per the ACP spec:

  • pending: "The tool call hasn't started running yet because the input is either streaming or awaiting approval"
  • in_progress: "The tool call is currently running"

The premature in_progress update causes ACP clients (e.g. agent-shell) to overwrite the permission dialog UI with a regular status display, making it impossible for the user to approve or reject the permission. The tool call then hangs indefinitely.

This affects all permission types — both external_directory and bash permissions exhibit the same behavior.

ACP log showing the race (same toolCallId):

① tool_call           → status: "pending",     title: "write",               kind: "edit"
② request_permission  → status: "pending",     title: "external_directory",  kind: "other"   (id: 2, awaiting response)
③ tool_call_update    → status: "in_progress", title: "write",               kind: "edit"    (arrives before client responds to ②)

Raw JSON showing ② and ③ arriving in the same TCP read (same batch):

{"jsonrpc":"2.0","id":2,"method":"session/request_permission","params":{"sessionId":"...","toolCall":{"toolCallId":"toolu_01Pi2R6Ko5TVVAX9SsWw1qe2","status":"pending","title":"external_directory","rawInput":{"filepath":"/tmp/dummy-test-primary.txt","parentDir":"/tmp"},"kind":"other","locations":[]},"options":[{"optionId":"once","kind":"allow_once","name":"Allow once"},{"optionId":"always","kind":"allow_always","name":"Always allow"},{"optionId":"reject","kind":"reject_once","name":"Reject"}]}}
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"...","update":{"sessionUpdate":"tool_call_update","toolCallId":"toolu_01Pi2R6Ko5TVVAX9SsWw1qe2","status":"in_progress","kind":"edit","title":"write","locations":[{"path":"/tmp/dummy-test-primary.txt"}],"rawInput":{"filePath":"/tmp/dummy-test-primary.txt","content":"hello from primary agent\n"}}}}

Same pattern for bash permission:

{"jsonrpc":"2.0","id":1,"method":"session/request_permission","params":{"sessionId":"...","toolCall":{"toolCallId":"toolu_016wFeFwJnkF5i8CzbknBbaz","status":"pending","title":"bash","rawInput":{},"kind":"execute","locations":[]},"options":[...]}}
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"...","update":{"sessionUpdate":"tool_call_update","toolCallId":"toolu_016wFeFwJnkF5i8CzbknBbaz","status":"in_progress","kind":"execute","title":"bash","locations":[],"rawInput":{"command":"opencode --version 2>/dev/null || which opencode 2>/dev/null","description":"Get OpenCode version"}}}}

Expected behavior

The tool call should remain status: "pending" until the ACP client responds to session/request_permission. Only after receiving the permission response with allow_once/allow_always should the status transition to in_progress.

Impact

This confuses ACP clients (e.g. agent-shell) — the permission dialog gets overwritten before the user can respond, causing the tool call to hang indefinitely.

Environment

macOS, Emacs 30.2 (agent-shell via ACP), OpenCode v1.2.6

Activity

  1. github-actions commented on Apr 21, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

  2. Eric1212 commented on Jun 21, 2026

    @Eric1212

    Not sure about that one but what about this fix ? @Seluj78

     // event.ts
    
     export class Subscription {
       private readonly shellSnapshots = new Map<string, string>()
       private readonly toolStarts = new Set<string>()
    +  private readonly pendingPermissions = new Map<string, string>()  // toolCallID → requestID
       private readonly permission: ACPPermission.Handler
       private started = false
    
       async handle(event: Event) {
         switch (event.type) {
           case "permission.asked":
    +        if (event.properties.tool?.callID)
    +          this.pendingPermissions.set(event.properties.tool.callID, event.properties.id)
             this.permission.handle(event)
             return
    +      case "permission.replied":
    +        for (const [callID, reqID] of this.pendingPermissions)
    +          if (reqID === event.properties.requestID) { this.pendingPermissions.delete(callID); break }
    +        return
           case "message.part.updated":
             return this.handlePartUpdated(event)
    
       private async runningTool(sessionId, part, cwd) {
    +    if (this.pendingPermissions.has(part.callID)) return  // keep "pending" until permission resolved
         const output = part.tool === "bash" ? shellOutputSnapshot(part.state) : undefined
    
  3. Seluj78 commented on Jun 21, 2026

    @Seluj78

    Maybe, I don't know about opencode's source :)

  4. Eric1212 commented on Jun 21, 2026

    @Eric1212

    Sorry @Seluj78 , i though you might have touched that part of code and was contributor... As you cited this issue and have managed your own issue in related subject.

    Then i kindly invite @nexxeln to take a look of the issue and my proposition as he committed a few times on event.ts.

    Thanks,
    Éric

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions