A plain claude-code spawn mints no session id, so the most common member on the fleet can never be resumed #214

Closed
opened 2026-08-31 17:40:20 +02:00 by ltms · 1 comment
Owner

Found while verifying #209. That ticket fixed the opencode half of agentSessionId; this is a separate, pre-existing gap on the claude-code side, and #209's fix does not touch it.

What happens

ClaudeCodeLauncher.applySessionIdentity (line ~287):

boolean resuming = resumeSessionId != null && !resumeSessionId.isBlank();
boolean named = sessionName != null && !sessionName.isBlank();
if (!resuming && !named) {
    return null; // no identity requested — keep the legacy launch byte-identical
}
...
String minted = UUID.randomUUID().toString();
argv.add("--session-id");
argv.add(minted);
return minted;

A session id is minted only when the caller passed sessionName or resumeSessionId. fleet_spawn treats both as optional and passes null when they are absent (FleetMcp.java:270, :823), so an ordinary fleet_spawn{profile, role, worktree, ticket} mints nothing and agentSessionId is null for the life of that member.

Unlike opencode, this does not resolve later. The claude-code handle returns a fixed field, so #209's lazy re-resolution has nothing to find.

Observed

Three sonnet (claude-code) members spawned today with an ordinary fleet_spawn — no sessionName — all appear in fleet_list with no agentSessionId field:

{"sessionId":"term_65a5917eaae702e","profile":"sonnet","role":"dev","state":"done",
 "branch":"worker/cb185-hostenvnames-2692b5-3","charterSource":"none","liveStatus":"idle", ...}

Why it matters

fleet_spawn{resumeSessionId} is documented as the way to relaunch a member onto its prior conversation, and fleet_list is documented as where you find the id to pass. For claude-code — the backend behind the subscription profiles and most of the fleet's spawns — that id is never there unless the lead happened to pass a sessionName at spawn time, which nothing prompts them to do.

So the resume feature is effectively opt-in at spawn, and the moment you want it is after a member has done work worth resuming — by which point it is too late. A lead who wants to continue a member's conversation has no path back to it.

The failure is quiet in the usual way: no id in the roster reads as "this backend does not support resume", which is not what is happening.

Not obviously a defect — read the comment first

// keep the legacy launch byte-identical is a deliberate choice, not an oversight, and it should be respected until someone establishes it is safe to change. The question this ticket asks is whether that conservatism is still worth its cost.

Minting a UUID and passing --session-id on every claude-code spawn would make every member resumable at no runtime cost. What needs checking before doing it:

  • Does --session-id change any behaviour beyond naming the session — transcript location, resume semantics, anything the launcher or the reaper depends on?
  • Does it interact with -n / --agent / the charter flags, given #120's history of mutually exclusive flags that fail at launch rather than at build?
  • Does anything downstream assume agentSessionId == null means "not resumable" and would start behaving differently?

If minting always turns out to be risky, the fallback is to make the gap visible rather than silent: have fleet_spawn say in its tool description that resume requires sessionName at spawn, and consider defaulting sessionName to the ticket slug the spawn already has.

Acceptance

  • A decision recorded either way, with the --session-id behaviour actually checked against the binary rather than reasoned about — see #120: an argv the tests accept is not an argv the binary accepts.
  • If minting becomes the default: a test that a plain spawn produces a non-null agentSessionId, and one live spawn confirming the member still starts and the id appears in fleet_list.
  • If not: the tool description and the Features page say plainly that claude-code resume must be requested at spawn.

Related: #209 (the opencode half), #120 (argv flags that fail only against the real binary).

Found while verifying #209. That ticket fixed the opencode half of `agentSessionId`; this is a separate, pre-existing gap on the claude-code side, and #209's fix does not touch it. ## What happens `ClaudeCodeLauncher.applySessionIdentity` (line ~287): ```java boolean resuming = resumeSessionId != null && !resumeSessionId.isBlank(); boolean named = sessionName != null && !sessionName.isBlank(); if (!resuming && !named) { return null; // no identity requested — keep the legacy launch byte-identical } ... String minted = UUID.randomUUID().toString(); argv.add("--session-id"); argv.add(minted); return minted; ``` A session id is minted **only** when the caller passed `sessionName` or `resumeSessionId`. `fleet_spawn` treats both as optional and passes `null` when they are absent (`FleetMcp.java:270`, `:823`), so an ordinary `fleet_spawn{profile, role, worktree, ticket}` mints nothing and `agentSessionId` is `null` for the life of that member. Unlike opencode, this does not resolve later. The claude-code handle returns a fixed field, so #209's lazy re-resolution has nothing to find. ## Observed Three `sonnet` (claude-code) members spawned today with an ordinary `fleet_spawn` — no `sessionName` — all appear in `fleet_list` with **no `agentSessionId` field**: ```json {"sessionId":"term_65a5917eaae702e","profile":"sonnet","role":"dev","state":"done", "branch":"worker/cb185-hostenvnames-2692b5-3","charterSource":"none","liveStatus":"idle", ...} ``` ## Why it matters `fleet_spawn{resumeSessionId}` is documented as the way to relaunch a member onto its prior conversation, and `fleet_list` is documented as where you find the id to pass. For claude-code — the backend behind the subscription profiles and most of the fleet's spawns — that id is never there unless the lead happened to pass a `sessionName` at spawn time, which nothing prompts them to do. So the resume feature is effectively opt-in **at spawn**, and the moment you want it is *after* a member has done work worth resuming — by which point it is too late. A lead who wants to continue a member's conversation has no path back to it. The failure is quiet in the usual way: no id in the roster reads as "this backend does not support resume", which is not what is happening. ## Not obviously a defect — read the comment first `// keep the legacy launch byte-identical` is a deliberate choice, not an oversight, and it should be respected until someone establishes it is safe to change. The question this ticket asks is whether that conservatism is still worth its cost. Minting a UUID and passing `--session-id` on every claude-code spawn would make every member resumable at no runtime cost. What needs checking before doing it: - Does `--session-id` change any behaviour beyond naming the session — transcript location, resume semantics, anything the launcher or the reaper depends on? - Does it interact with `-n` / `--agent` / the charter flags, given #120's history of mutually exclusive flags that fail at launch rather than at build? - Does anything downstream assume `agentSessionId == null` means "not resumable" and would start behaving differently? If minting always turns out to be risky, the fallback is to make the gap visible rather than silent: have `fleet_spawn` say in its tool description that resume requires `sessionName` at spawn, and consider defaulting `sessionName` to the ticket slug the spawn already has. ## Acceptance - A decision recorded either way, with the `--session-id` behaviour actually checked against the binary rather than reasoned about — see #120: an argv the tests accept is not an argv the binary accepts. - If minting becomes the default: a test that a plain spawn produces a non-null `agentSessionId`, and one live spawn confirming the member still starts and the id appears in `fleet_list`. - If not: the tool description and the Features page say plainly that claude-code resume must be requested at spawn. Related: #209 (the opencode half), #120 (argv flags that fail only against the real binary).
Author
Owner

Merged to main as bf0e09b and now proven live: fleet_list reports agentSessionId: c73c8bbf-57f4-4223-b6f3-99a33ab71dce for a plain fleet_spawn that passed neither sessionName nor resumeSessionId. That is what this ticket asked for.

The live proof was blocked for a while, and the reason is worth recording here: adding --session-id <uuid> pushed the launch command from 978 to 1028 bytes, and herdr types that command into the pane, where a pty line buffer cuts it at 1024. The tail that got cut was --autocompact 250000, which arrived as --autocompact 25; claude rejected it and exited, so every claude-code spawn died as an unexplained readiness timeout.

This ticket's change was correct and stayed. The limit it exposed is #220, fixed on main @ c3fa113.

Merged to main as `bf0e09b` and now **proven live**: `fleet_list` reports `agentSessionId: c73c8bbf-57f4-4223-b6f3-99a33ab71dce` for a plain `fleet_spawn` that passed neither `sessionName` nor `resumeSessionId`. That is what this ticket asked for. The live proof was blocked for a while, and the reason is worth recording here: adding `--session-id <uuid>` pushed the launch command from 978 to 1028 bytes, and herdr **types** that command into the pane, where a pty line buffer cuts it at 1024. The tail that got cut was `--autocompact 250000`, which arrived as `--autocompact 25`; claude rejected it and exited, so every claude-code spawn died as an unexplained readiness timeout. This ticket's change was correct and stayed. The limit it exposed is #220, fixed on main @ `c3fa113`.
ltms closed this issue 2026-09-01 09:12:48 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: fleet/fleetd#214