Skip to content

rpc: open_session cannot accept a caller-chosen durable session id, so every embedder maintains a second identity #1951

Description

@code-yeongyu

Summary

open_session gives a caller no way to choose the durable session id. The host always mints its own uuidv7, so every embedder that already has a stable record id for the conversation (a desktop thread, a server-side job row, an external tracker item) is forced to keep a SECOND identity and a translation layer between the two. Every feature that crosses the boundary — goal files, subagent/team matching, resume cursors, adoption provenance — then has to carry the mapping, and when the mapping is lost the user-visible result is a dead-end turn rather than a recoverable error.

Expected (the ideal state)

open_session accepts an optional caller-chosen durable id, negotiated by capability, validated, and refused cleanly when it would collide.

Acceptance criteria:

  1. open_session accepts an optional durableSessionId. When present and the session is being CREATED, the session's durable id IS that value: the open_session reply carries state.sessionId === durableSessionId and the JSONL header records it.
  2. The field is named durableSessionId, never sessionId. session-command-router.ts:250 reads "sessionId" in command generically and feeds it to drain gating (:252), active-request accounting (:256-260) and runWithSessionAttribution (:257); a caller-chosen durable id arriving under that key would be treated as a routing handle. The name also matches what list_sessions already publishes.
  3. Advertised as host capability durable_session_id in get_protocol_info, so a client can detect support instead of guessing.
  4. Validation: a malformed id is refused with a stable invalid_session_id code, using the existing assertValidSessionId rule.
  5. Collision safety: if a LIVE session already holds that durable id, the open is refused with a stable session_id_in_use code. Two live sessions sharing one durable id would collide every per-session artifact keyed by it.
  6. Resume is unaffected: when sessionPath names an existing session file, the header's id stays authoritative and durableSessionId is ignored rather than rewriting history.
  7. Path reservations remain the single-writer authority; this change adds an id guard, it does not replace session_path_in_use.
  8. The D1 normative table and the D6 identity notes in rpc-mode.ts document the field, the capability and both new error codes.

Actual

  • open_session params are sessionPath?, cwd?, provider?, modelId?, thinkingLevel?, permissionPreset?, retain_on_disconnect?, kind?, context?, auto_title? — no id field (packages/coding-agent/src/modes/rpc/rpc-types.ts:267-313).
  • The id is always host-minted: createSessionId() { return uuidv7() } (packages/coding-agent/src/core/session-manager.ts:313-315), assigned at :1031.

Evidence

  • Id injection already exists in-process but is unreachable over RPC: NewSessionOptions.id (core/session-manager.ts:99-102), honored in _resetToNewSession (:1025-1031) behind assertValidSessionId (:317-322); SessionManager.create(cwd, sessionDir?, options?) already takes it (:2052).
  • The RPC seam that drops it: modes/rpc/session-registry.ts:341 — sessionPath ? SessionManager.open(sessionPath, undefined, cwd) : SessionManager.create(cwd), neither call passing NewSessionOptions.
  • SessionManager.open(path, sessionDir?, cwdOverride?) (:2063) has no options parameter at all. This is the branch that matters: a client that names its own session file always lands here, and a missing file falls through to _setSessionFile -> _resetToNewSession() (:1007) with no options, minting a fresh id.
  • When the file already exists the header id is adopted (:991), which is why resume must stay untouched.

Root cause

The RPC surface was designed with the path as the only caller-supplied identity. Id injection was implemented one layer below (the session manager and the session repo) and never lifted to the wire.

Scope

In: the open_session field, capability advertisement, validation, the live-id collision guard, threading the id through RpcSessionLaunchProfile into both manager constructors, docs, tests.
Out: changing how ids are generated when the caller supplies none; any change to path reservation semantics; any client-side adoption (tracked separately in the desktop repo).

Related

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

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions