siyuan-note/siyuan · error · Error

Failed to remove agent session

Error message

Failed to remove agent session

What it means

Thrown by SessionStore.remove() after waitForPendingSave(id) when POST /api/ai/agent/removeSession returns null or a non-zero code. The pending save is awaited first so the server is not asked to delete a session that still has an in-flight write, but a non-zero reply (e.g. session already gone, permission denied) still trips this guard.

Source

Thrown at app/src/layout/dock/agent/SessionStore.ts:203

            }
            return {revision, session: resp.data?.session};
        };
        const save = previous ? previous.then((result) => persist(result.revision)) : persist(baseRevision);
        sessionSaveQueues.set(snapshot.id, save);
        try {
            return await save;
        } finally {
            if (sessionSaveQueues.get(snapshot.id) === save) {
                sessionSaveQueues.delete(snapshot.id);
            }
        }
    },

    async remove(id: string): Promise<void> {
        await waitForPendingSave(id);
        const resp = await fetchSyncPost(API + "/removeSession", {id}, APP_HEADER) as {code: number; msg?: string};
        if (!resp || resp.code !== 0) {
            throw new Error(resp?.msg || "Failed to remove agent session");
        }
        sessionRevisions.delete(id);
        sessionRuntimeRevisions.delete(id);
    },

    async rename(id: string, newTitle: string): Promise<void> {
        const session = await this.load(id);
        if (!session) { return; }
        session.title = newTitle;
        await this.save(session);
    },

    async setPermission(id: string, permissionMode: AgentPermissionMode): Promise<AgentPermissionMode> {
        await waitForPendingSave(id);
        const resp = await fetchSyncPost(API + "/setPermission", {
            sessionID: id,
            permissionMode,
        }, APP_HEADER) as {code: number; msg?: string; data?: {permissionMode?: AgentPermissionMode}};

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Treat a 'not found' / already-removed reply as success: clear local caches (sessionRevisions/sessionRuntimeRevisions) and remove the row from the UI.
  2. Surface resp.msg so the user sees the kernel's actual reason instead of the generic string.
  3. Verify the kernel is reachable and the session id matches the listing returned by /lsSessions before issuing remove.
  4. Retry once after re-establishing the WebSocket if the failure was a transient transport drop.

Example fix

// before
await SessionStore.remove(id);
// after
try {
    await SessionStore.remove(id);
} catch (e) {
    if (/not found|already/i.test(e.message)) { sessionRevisions.delete(id); sessionRuntimeRevisions.delete(id); }
    else { showMessage(e.message); throw e; }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!id || typeof id !== 'string') throw new Error('remove: id required');
const exists = (await SessionStore.list({keyword: id})).sessions.some(s => s.id === id);

Type guard

function isSessionId(v: unknown): v is string { return typeof v === 'string' && v.length > 0; }

Try / catch

try { await SessionStore.remove(id); }
catch (e) {
    if (/not found|already/i.test(e.message)) { sessionRevisions.delete(id); sessionRuntimeRevisions.delete(id); }
    else showMessage(e.message);
}

Prevention

When it happens

Trigger: Removing a session id that was already deleted on the server, removing while the kernel rejects the request (auth/permission), removing when the kernel process is down, or removing a session whose id is malformed.

Common situations: User clicks delete on two clients simultaneously; one succeeds and the second hits a non-zero 'not found' code; workspace switched but the UI still holds stale ids; kernel restart between load and remove.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/b6b6a16b1322158b. Report an issue: GitHub.