Skip to content

ACP: session/new returns a provider-prefixed sessionId that session/load cannot resolve #6174

Description

@sequico

Description

An ACP client cannot resume a session it created with session/new: the id session/new returns is not resolvable by session/load, while session/list reports the bare form that is.

Three observations from one ACP server, all in a throwaway CODEWHALE_HOME (no turn sent, no model call, no real session store read or written):

  1. session/new returns a provider-prefixed id.
  2. session/list returns bare uuids.
  3. session/load accepts only the bare form. The prefixed form fails with -32602, and it still fails when a session file is named exactly like the prefixed id.

An ACP client has no id for a session it just created other than the one session/new returned, so a client that persists and replays that id — the normal thing to do — can never resume the session.

Steps to reproduce

Everything below runs against a scratch home; nothing outside it is touched.

export CODEWHALE_HOME=$(mktemp -d)
ac() { printf '%s\n' "$@" | codewhale serve --acp; }

1. What session/new advertises:

ac '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true}}}}' \
   '{"jsonrpc":"2.0","id":2,"method":"session/new","params":{"cwd":"<any-directory>","mcpServers":[]}}'
{"jsonrpc":"2.0","id":2,"result":{"sessionId":"codewhale-95bb3fa8-8784-4eed-b34a-59072e9e9823","modes":{...},"models":{...}}}

Note the codewhale- prefix on the id.

2. What session/list advertises for existing sessions:

{"jsonrpc":"2.0","id":2,"result":{"sessions":[{"sessionId":"c45e7916-ab6a-4585-8d09-d3649fd92f38","title":"...","cwd":"...","createdAt":"...","updatedAt":"...","messageCount":232}, ...]}}

Bare uuid, no prefix.

3. What session/load accepts. With exactly one persisted session file present, 415488d4-14a0-46ff-8477-7a3989a36de2.json:

bare id — succeeds:

{"jsonrpc":"2.0","id":2,"result":{"sessionId":"415488d4-14a0-46ff-8477-7a3989a36de2","modes":{...},"models":{...},"configOptions":[...]}}

the same id written the way session/new writes it — fails:

{"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"could not load session codewhale-415488d4-14a0-46ff-8477-7a3989a36de2: No session found with prefix: codewhale-415488d4-14a0-46ff-8477-7a3989a36de2"}}

Adding a second file named codewhale-415488d4-14a0-46ff-8477-7a3989a36de2.json and repeating the prefixed request gives the identical error, so this is not merely a filename-naming mismatch:

415488d4-14a0-46ff-8477-7a3989a36de2.json
codewhale-415488d4-14a0-46ff-8477-7a3989a36de2.json
{"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"could not load session codewhale-415488d4-14a0-46ff-8477-7a3989a36de2: No session found with prefix: codewhale-415488d4-14a0-46ff-8477-7a3989a36de2"}}

Expected behavior

One of:

  • session/new returns the id session/load resolves — the bare uuid session/list reports — or
  • session/load accepts the prefixed form session/new hands out (strip a known <provider>- prefix before resolving), or
  • the resolver recognises the prefixed form and, when the remainder resolves to an existing session, says so instead of reporting a plain not-found.

Actual behavior

The advertised id is unresolvable. A client that round-trips it gets -32602 ... No session found with prefix: codewhale-<uuid>.

Impact

Any ACP client that persists session ids (Paseo, editors) cannot resume the CodeWhale sessions it created.

It also does not stay contained. A client that polls a timeline for an agent whose resume fails re-attempts the resume on every poll, so the misadvertised id turns into a permanent error loop. In the case that led me here, one level-50 entry was written every ~30s for over five hours, and the affected timeline request was pinned at ~390 ms p50 because every poll paid for a failing ACP resume. (That client-side behaviour is reported separately; the trigger is this id mismatch.)

Environment

  • OS: Linux 7.2.4-4-liquorix-amd64 (x64)
  • codewhale version: 0.9.12 (dcd4c20)
  • Install method: pnpm global. The package downloads the GitHub release binary (bin/downloads/codewhale.source = source=github, base=https://github.com/Hmbown/CodeWhale/releases/download/v0.9.12/)
  • codewhale doctor summary:
    Version Information:
      codewhale-tui: 0.9.12 (dcd4c200f72f)
      rust: rustc 1.94.0 (4a4ef493e 2026-03-02)
    Configuration:
      config.toml found at ~/.codewhale/config.toml
    Resolved User Paths (read-only):
      sessions: ~/.codewhale/sessions
    
  • Model/provider: not exercised — initialize / session/new / session/load / session/list only, no turn sent
  • Terminal app: n/a (stdio ACP client, not the TUI)
  • Shell: bash

Logs, screenshots, or recordings

Raw ACP responses are inlined in the steps above. For the shape of the failure as a client sees it, one raw daemon-side line (Paseo 0.8.0) from the episode above, with ids and paths replaced by placeholders:

{"level":50,"time":<ts>,"hostname":"<host>","daemonVersion":"0.8.0","module":"websocket-server","connectionId":"<conn>","transport":"direct","peer":"loopback","clientId":"<client>","module":"session","sessionId":"<client-session>","err":{"type":"Error","message":"could not load session codewhale-14a4e0de-...: No session found with prefix: codewhale-14a4e0de-...","stack":"ACPRequestError: could not load session codewhale-14a4e0de-...: No session found with prefix: codewhale-14a4e0de-...\n    at toACPRequestError (.../acp-agent.js:66:18)\n    at ACPAgentSession.runACPRequest (.../acp-agent.js:1878:19)\n    at process.processTicksAndRejections (node:internal/process/task_queues:104:5)\n    at async ACPAgentSession.initializeResumedSession (.../acp-agent.js:1070:34)\n    at async GenericACPAgentClient.resumeSession (.../acp-agent.js:503:9)\n    at async Object.resumeSession (.../provider-registry.js:272:107)\n    at async AgentManager.resumeAgentFromPersistenceInternal (.../agent-manager.js:724:25)","name":"ACPRequestError"},"agentId":"<agent-id>","msg":"Failed to handle fetch_agent_timeline_request"}

One caveat, for honesty. I did not send a turn, so I did not observe a session created by session/new end-to-end (persisted, then resumed). The link between step 1 and step 3 is established at the interface — the form session/new returns versus the only form session/load accepts — both directly observed. Steps 1, 2 and 3 are raw command output, not inference.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions