From c68c3dbb43e2b9137a37a3f0d8b9daab154123a6 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 2 Aug 2026 12:22:41 +0800 Subject: [PATCH] fix(host): check subagent ownership before cwd conflict in ensureSession MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Explicit-id adoption of a cold session-backed subagent under a *different* cwd answered `session-conflict` because the cwd check ran before the persistence inspection classified the identity. The api/commands.ts contract states explicit-id `session.create` adoption rejects session-backed subagents with `agent-busy` — ownership is an identity property, so it must win regardless of the requested workspace. Reorder the stored-session branch to inspect and classify ownership first, then enforce the cwd match, making the response match the documented contract. --- packages/host/apiproxy/src/api-proxy.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/host/apiproxy/src/api-proxy.ts b/packages/host/apiproxy/src/api-proxy.ts index 517688de34..501390e488 100644 --- a/packages/host/apiproxy/src/api-proxy.ts +++ b/packages/host/apiproxy/src/api-proxy.ts @@ -1066,12 +1066,20 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro } async function agentFor(sessionId: SessionId): Promise<{ agent: Agent } | { error: RpcError }> { - const attached = ctx.sessions.get(sessionId) const live = ctx.agents.get(sessionId) - if (attached !== undefined && hasSubagentOwner(attached, live)) { + if (live !== undefined) { + // Fence the live agent's own session rather than trusting a + // "registered ⇒ attached-store" invariant: a registered subagent whose + // session is ever absent from the attached store must still not be + // handed out through generic Host routing (ensureSession's `.catch` + // already fences `live.session`; this is the same check on the fast path). + if (hasSubagentOwner(live.session, live)) return { error: subagentOwnershipError(sessionId) } + return { agent: live } + } + const attached = ctx.sessions.get(sessionId) + if (attached !== undefined && hasSubagentOwner(attached, undefined)) { return { error: subagentOwnershipError(sessionId) } } - if (live !== undefined) return { agent: live } let resume = resumes.get(sessionId) if (resume === undefined) { resume = (async () => {