Go to file
Ben Brandt 7e857d59ec fix: end a v2 turn once when Claude Code folds a prompt into it (#1618)
Fixes #1611.

## Problem

On ACP v2, a prompt sent while the session works gets its reply twice.
The client sees:

1. the reply, streamed under the model's `msg_` id
2. `state_update` `idle` with `stopReason: end_turn` and zero usage
3. `state_update` `running`
4. the same reply again, under a UUID
5. `state_update` `idle` with the real usage

This happens whenever Claude Code folds the prompt into running work.
The work can be a cycle that Claude Code started on its own, as in the
issue, or the turn of an earlier prompt.

## Cause

Claude Code sends one result, not two. The order of its frames differs
for a folded prompt (CLI 2.1.323):

| Prompt | Order |
| --- | --- |
| started the work | `result`, then `command_lifecycle` `completed` |
| folded into the work | `command_lifecycle` `completed`, then `result`
|

`V2Session.lifecycle` took every `completed` frame of the last started
prompt as the end of a turn that had no result, and closed the turn. The
result then found no open turn. It opened a new one, saw no streamed
text in it, and sent `result.result` as the reply.

## Fix

The session remembers which prompt started Claude Code's current cycle
(`cycleCommand`). Only that prompt's `completed` frame closes a turn
that had no result. A folded prompt's `completed` frame leaves the turn
open, and the result ends it.

A result ends the cycle, so the next prompt of a turn that stays open
(the `/clear` and the plan prompt of a fresh context) starts its own.

The `cancelled` frame is unchanged: it still closes the turn for the
prompt that started last.

## Tests

- `v2.test.ts`: one `it.each` with the frames in the CLI's order, for a
prompt folded into another prompt's turn and into a cycle that Claude
Code started on its own. Both fail without the fix with `running, idle,
running, idle`.
- `v2-integration.test.ts`: one case against the bundled CLI. It passes
with the fix.
- Live, with the issue's steps: one reply and one `idle` with the real
usage.

## Not covered

- I did not capture what Claude Code sends when a cancel arrives during
work with a folded prompt.
- I observed the frame order on CLI 2.1.323 only.

ACP v1 is not affected: it does not end a turn at a `completed` frame.
2026-11-13 13:48:52 +00:00
2025-09-02 19:06:32 -03:00
2026-03-26 12:19:06 +01:00

ACP adapter for the Claude Agent SDK

npm

Use Claude Agent SDK from ACP-compatible clients!

This tool implements an ACP agent by using the official Claude Agent SDK, supporting:

  • Context @-mentions
  • Images
  • Tool calls (with permission requests)
  • Compact file changes through the negotiated AIR diff patch extension
  • Following
  • Edit review
  • TODO lists
  • Nested subagent transcripts
  • Interactive (and background) terminals
  • Custom Slash commands
  • Client MCP servers
  • An Ultracode session config option, which turns on Claude Code's dynamic workflows for every task. It appears when dynamic workflows are enabled and the model supports ultracode (any model with xhigh effort)
  • /mcp in the chat: the MCP server status as a list. The adapter runs /mcp reconnect, /mcp enable, and /mcp disable through the SDK control API, because Claude Code refuses them in SDK mode. A reconnect of an ACP server that needs authentication starts MCP OAuth through URL elicitation
  • Session-scoped long-running goals for AIR through the goal extension under _meta.jetbrains.air.goal
  • Structured errors, recovery, and warnings through the opt-in session failure extension
  • Concrete model and effort defaults through the opt-in recommended config value extension
  • Tool permission presentation, editable choices, and durable effects through the permission extension
  • One fact per field in every tool call report, as the ACP tool call contract defines
  • All AIR extensions, capabilities, and _meta keys: AIR extensions

Learn more about the Agent Client Protocol.

To try changes that have landed on main but are not released yet, install from the preview channel — every push to main publishes one. See docs/RELEASES.md.

npm install @agentclientprotocol/claude-agent-acp@preview

Standalone executables

Each release attaches archives for Linux (glibc and musl), macOS and Windows, on x64 and arm64, to its GitHub release. They need no Node.js install. Each holds two files: claude-agent-acp, and the Claude CLI (claude) it runs. Keep them in the same directory; a symlink to claude-agent-acp elsewhere works. To use a Claude CLI you already have instead, set CLAUDE_CODE_EXECUTABLE. On Alpine, install libgcc and libstdc++ first.

To build one for your machine (needs Bun) and check it:

npm run build:binary
npm run smoke:binary            # add `-- --full` to drive a real session; needs a Claude login

Experimental ACP v2

The adapter also serves the draft ACP v2: a client that asks for protocol version 2 or later in initialize gets v2, and every other client gets v1. The draft can still change, and this surface changes with it.

CLAUDE_AGENT_ACP_PROTOCOL limits the adapter to one version. With v1, a client that asks for v2 is negotiated down to v1. With v2, the adapter rejects the initialize of a v1 client. auto, the default, serves both.

On v2 the adapter serves the session baseline (session/new, session/list, session/resume with replay, session/close, session/prompt, session/cancel), session/delete, session/fork, session/status, session/options (with initial configOptions on session/new and session/resume), session/set_title, additional directories, config options (the mode is one), plans, notices, compaction, display terminals for commands, permission requests, elicitation, providers, auth, and subagents. Steering is v1 only for now.

v2 is served by the v2 agent in src/v2/, which runs straight on Claude Code instead of translating to v1. A v2 client that declares JetBrains AIR (_meta.jetbrains.air) also gets the AIR extensions that ACP v2 does not cover.

Contribution Policy

This project does not require a Contributor License Agreement (CLA). Instead, contributions are accepted under the following terms:

By contributing to this project, you agree that your contributions will be licensed under the Apache License, Version 2.0. You affirm that you have the legal right to submit your work, that you are not including code you do not have rights to, and that you understand contributions are made without requiring a Contributor License Agreement (CLA).

Description
Pull mirror of agentclientprotocol/claude-agent-acp (Apache-2.0): the ACP bridge that lets Buzz drive Claude Code
Readme 47 MiB
Languages
TypeScript 98.9%
JavaScript 1%
Shell 0.1%