Skip to content

bug(desktop): Side Conversation creation fails for sessions with linked child agents #3654

Closed

Description

@liuxiaocs7

What happened

On current main, opening a Side Conversation from a settled Desktop Session fails when the retained source history contains linked subagent / Agent Graph child Sessions.

The UI shows only the generic message:

Could not open the side chat. Please try again.

The underlying Runtime Host error is:

operation_unavailable:
Ordinary branches cannot share linked child Session ownership with their source

This still fails after the root Turn and all child agents have completed. Retrying later therefore does not resolve this path.

Verified against main at c79e9eb40ee4a0fcd204fb075427d635989eaaca.

How to reproduce

  1. Build and launch Desktop from current main.
  2. Start a normal conversation.
  3. Run a workflow that creates linked child Sessions, for example an Agent Graph/code-review flow with multiple subagents.
  4. Wait until the root Turn and child agents have all reached terminal state.
  5. Open Side Chat from the titlebar/workbar, or invoke /side.
  6. Observe that the Side Conversation is not created and the generic setup-error banner appears.

A normal settled conversation without linked child Sessions can create the Side Conversation successfully.

Expected behavior

A Side Conversation should open from the latest completed source Turn and use the retained parent history as reference-only context, without taking ownership of or controlling the source Session's child agents.

Actual behavior and root cause

The renderer correctly marks the request as a Side Conversation:

The Desktop IPC adapter does not carry that intent into Runtime Host:

  • it calls copySession("branch", ...) without a Side Conversation purpose;
  • sideConversation is used only for cleanup ownership and for adding mode:side_conversation metadata after the copy succeeds:
    ipcMain.handle(
    "sessions:branchFromTurn",
    async (event, sessionId: string, input: unknown) => {
    const normalized = normalizeRuntimeHostBranchFromTurnInput(input);
    const createBranch = () =>
    deps.client.copySession("branch", {
    sourceSessionId: sessionId,
    targetSessionId: normalized.copyId,
    sourceTurnId: normalized.sourceTurnId,
    });
    let branch = normalized.sideConversation
    ? await deps.sessionCopyCleanup.ownCreation(
    {
    sessionId: normalized.copyId,
    kind: 'branch',
    sourceSessionId: sessionId,
    sourceTurnId: normalized.sourceTurnId,
    ownerId: bindCopyOwner(event),
    },
    createBranch,
    )
    : await createBranch();
    if (normalized.name || normalized.sideConversation) {
    branch = await deps.client.updateSessionMetadata(branch.id, {
    ...(normalized.name ? { name: normalized.name } : {}),
    ...(normalized.sideConversation
    ? {
    labels: [
    ...new Set([
    ...branch.labels,
    SIDE_CONVERSATION_SESSION_LABEL,
    ]),
    ],
    }
    : {}),
    });

The Host protocol has no field that distinguishes a Side Conversation from an ordinary branch:

Runtime Host therefore applies the ordinary-branch ownership rule and rejects the copy:

The renderer then catches every creation exception and collapses it to fork_setup_failed, hiding the actionable Host code:

The ordinary-branch rejection is a valid safety invariant: an independent branch must not share child Session ownership with its source. The defect is that Side Conversation currently reaches that path as an ordinary branch even though its intended semantics are an ephemeral, reference-only context copy.

Suggested fix

Introduce an explicit Host-recognized copy purpose or operation for Side Conversations, rather than inferring the behavior from a label added after publication. For example:

type SessionConversationCopyPurpose =
  | "ordinary"
  | "side_conversation";

The exact protocol shape is open to discussion, but the intent should:

  1. travel from Renderer → Desktop IPC → Runtime Host;
  2. be included in the idempotency fingerprint;
  3. be persisted with the conversation-copy publication metadata;
  4. cause the Host to atomically apply the Side Conversation label/semantics.

For retained linked-child results, the Side Conversation path should create an ownership-free historical snapshot:

  • retain the terminal child result's user-visible status, summary, and bounded output needed as model context;
  • do not retain control/ownership identifiers such as the source childSessionId, runId, or resumedFromRunId;
  • copy and rewrite any artifact content that must remain accessible from the target Session;
  • cover typed message results, RuntimeEvent function responses, archived tool results, and any copied ledger projections;
  • never clone, reparent, delete, resume, or otherwise control the source child Session.

If a retained Graph, child Session, or child result is still live, return the existing retryable session_busy result. Missing or inconsistent terminal provenance should continue to fail closed.

Ordinary branches should keep their current rejection behavior, and revisions should keep their existing validated terminal-reference behavior.

Error-handling follow-up

The direct bug requires the Host copy semantics above; retrying in the renderer alone cannot fix a permanent operation_unavailable result.

The Desktop path should additionally preserve structured failure codes:

  • session_busy: show a waiting/retryable state and retry when the source becomes idle;
  • operation_unavailable: show a specific unsupported-context error;
  • persistence/internal failures: show the generic setup error.

A later successful fork should also clear any previous setup banner.

There is a separate edge case in which latestSettledTurnId() documents “start empty” when no completed Turn exists, while the current caller returns fork_setup_failed. That can be handled alongside this work or tracked separately.

Acceptance criteria

  • A settled source Session containing one or more terminal linked child Sessions can create a Side Conversation.
  • The created Side Conversation contains useful historical child-result context without retaining child ownership/control references.
  • Removing the source Session or the Side Conversation cannot incorrectly remove child Sessions owned by the other.
  • Ordinary branches with linked children remain rejected.
  • Revisions preserve their current validated terminal-reference behavior.
  • A live source Graph or child returns a structured, retryable session_busy.
  • Copy retries remain idempotent, and reusing the same target identity with a different copy purpose is rejected.
  • Failed or interrupted creation leaves no visible/preparing orphan Session.
  • A Runtime Host integration test covers at least one source Session with a terminal Agent Graph child and verifies successful Side Conversation creation plus ownership isolation.
  • Renderer tests verify error-code preservation and clearing a stale setup banner after success.

Related work

No existing public Maka issue, PR, or Discussion was found that tracks this exact Side Conversation + linked-child integration failure.

Image

Activity

  1. liuxiaocs7 commented on Aug 23, 2026

    @liuxiaocs7
    MemberAuthor

    I'll try to fix this problem

  2. added 5 commits that reference this issue on Aug 24, 2026
    a8f763c
    2e586ff
    3d4bed3
    31d2aa0
    432a238
  3. added a commit that references this issue on Aug 24, 2026
    f37f5c3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions