Skip to content

fix(acp): don't fail the loop when an agent lacks session/set_mode - #68

Merged
alistairstead merged 2 commits into
mainfrom
claude/github-issue-67-s8xkrg
Aug 31, 2026
Merged

alistairstead merged 2 commits into
mainfrom
claude/github-issue-67-s8xkrg

Conversation

@alistairstead

@alistairstead alistairstead commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #67

Problem

ralph run aborts on iteration 1 against any agent that doesn't implement session modes:

ERROR service=default e=message is not an Object. (evaluating '"method" in message') rejection
ERROR service=adapter-source error=Method 'session/set_mode' not found on_error
ERROR service=session-machine iteration=1 error=Method 'session/set_mode' not found iteration_error

Root cause

Two independent bugs, both reproduced in the new tests before fixing.

1. session/set_mode sent to agents that never advertised modes. The guard in AcpAdapter.run() was:

if (preferredMode && sessionResponse.modes?.currentModeId !== preferredMode)

When an agent advertises no modes, modes is undefined, so undefined !== "acceptEdits" is true and the request goes out anyway. Session modes are optional in ACP — an agent only has to accept modes it advertised in session/new — so the agent answers Method 'session/set_mode' not found, which threw out of run() and failed the iteration.

2. A bare JSON scalar on the agent's stdout kills the connection. ndJsonStream enqueues every line that parses as JSON, including scalars like "some log line". The SDK dispatcher then evaluates "method" in message, which throws a TypeError on a primitive and escapes its receive loop as an unhandled rejection — the message is not an Object line above.

Changes

Protocol robustness

  • applyPreferredMode() only sends session/set_mode when the preferred mode appears in availableModes and isn't already current; a rejected call is logged as a warning instead of aborting the iteration. Mode selection is a nicety, not a requirement for the loop to run.
  • New adapters/acp/message-filter.ts drops non-object messages before they reach the SDK dispatcher.

Point the Claude adapter at the maintained agent

The adapter targeted claude-code-acp, the binary from @zed-industries/claude-code-acp. That package is deprecated on npm — renamed to @agentclientprotocol/claude-agent-acp, which ships a claude-agent-acp binary, tracks ACP SDK 1.x, and was last published this month (the old package's last release was February).

  • ClaudeAcpAdapter.command is now claude-agent-acp.
  • AcpAdapter gained a fallbackCommands list: claude-code-acp still resolves for users who have the deprecated package installed, with a warning naming the maintained one. isAvailable() and the spawn in run() share that resolution, so a fallback install is detected rather than reported missing.
  • Adapters also gained an installHint, printed by ralph run when no candidate binary is on PATH.

Docs

README and the installation guide name the maintained package and disambiguate the three similarly-named ones: @agentclientprotocol/claude-agent-acp (maintained), @zed-industries/claude-code-acp (deprecated rename, still accepted), and the unrelated third-party claude-code-acp on npm that installs cc-acp — installing that one and symlinking it is what led the reporter to an agent that doesn't implement the protocol.

Tests

packages/cli/src/adapters/acp.test.ts drives the adapter against a fake ACP agent subprocess whose behaviour is set by flags (advertises modes or not, accepts or rejects set_mode, prints a bare JSON scalar or not). Four of the five cases failed with the exact errors from the issue before the fix. Three further tests cover primary/fallback/missing command resolution.

Verification

Locally, matching CI: bun run lint, bun run test (582 pass), bun turbo typecheck, and bun run --cwd packages/cli build --single all clean.

Note on dependencies

Bumping @agentclientprotocol/sdk does not fix this. The SDK added the non-object guard in v1.0.0 (ralph pins ^0.14.1), so a bump would resolve bug 2 — but bug 1 is ralph's own guard logic, and the SDK forwards the agent's Method not found error either way. The 0.14 → 1.4 bump is a major-version jump and is left out of this fix.

claude added 2 commits August 31, 2026 22:14
Ralph asked every agent to switch into its preferred session mode as soon
as the session opened, guarded only by `modes?.currentModeId !== preferred`.
That guard is true when the agent advertises no modes at all, so agents
without session-mode support got a `session/set_mode` request they answer
with "Method 'session/set_mode' not found" — aborting iteration 1.

Session modes are optional in ACP, and an agent only has to accept modes it
advertised in `session/new`. Only send `session/set_mode` when the preferred
mode is in `availableModes` and isn't already current, and treat a rejected
call as a warning rather than an iteration error.

Also stop a chatty agent from killing the connection: `ndJsonStream`
enqueues any line that parses as JSON, including bare scalars, and the SDK
dispatcher's `"method" in message` throws a TypeError on a primitive that
escapes its receive loop as an unhandled rejection ("message is not an
Object"). Non-object messages are now dropped before they reach the SDK.

Adapters gained an install hint, surfaced by `ralph run` when the ACP binary
is missing, and the docs now spell out that Claude Code needs the scoped
`@zed-industries/claude-code-acp` package — the unscoped `claude-code-acp`
package on npm is an unrelated project that installs `cc-acp`.

Covered by new adapter tests driving a fake ACP agent subprocess.

Fixes #67

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NHqjSU8nVvcMyAYNr6XEb9
`@zed-industries/claude-code-acp` is deprecated on npm — it was renamed to
`@agentclientprotocol/claude-agent-acp`, which ships a `claude-agent-acp`
binary, tracks ACP SDK 1.x, and was last published this month (the old
package's last release was February).

Point the Claude adapter at `claude-agent-acp` and add a general
`fallbackCommands` list to AcpAdapter so an adapter can accept older binary
names: `claude-code-acp` still resolves for users who have the deprecated
package installed, with a warning naming the maintained one. `isAvailable()`
and the spawn in `run()` now share that resolution, so a fallback install is
detected rather than reported missing.

Docs and README name the maintained package and explain the three
similarly-named packages: the maintained one, the deprecated rename, and the
unrelated third-party `claude-code-acp` on npm that installs `cc-acp`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NHqjSU8nVvcMyAYNr6XEb9
@alistairstead
alistairstead merged commit 6fcc4c4 into main Aug 31, 2026
6 checks passed
@alistairstead
alistairstead deleted the claude/github-issue-67-s8xkrg branch August 31, 2026 22:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

claude-code-acp: Method 'session/set_mode' not found

2 participants