From 76a406b78432c471b6bf30fab5fb20ee200ec671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Emre=20Kabakc=C4=B1?= Date: Mon, 4 May 2026 02:46:50 +0100 Subject: [PATCH 1/2] fix(chat-instance): wrap boot-time startInstance in orgContext MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #506's chat-instance-manager.ts:initialize loads connections at gateway boot and calls startInstance() → resolveConfigForRuntime() → secretStore.get(). Since #516 made PostgresSecretStore resolve secrets per-org via AsyncLocalStorage, boot-time reads have no org context, fall back to the GLOBAL bucket, and find nothing — every connection written under an org's context is then marked status='error' on first restart. Resolve the agent's organization_id via getAgentOrganizationId() and wrap the startInstance call in orgContext.run({organizationId}). Pre-org rows (no template agent or no org) keep the GLOBAL bucket fallback. Verified: bun test packages/server/src/gateway → 502 pass / 0 fail. --- .../connections/chat-instance-manager.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/server/src/gateway/connections/chat-instance-manager.ts b/packages/server/src/gateway/connections/chat-instance-manager.ts index a0fce5114..ce6793b92 100644 --- a/packages/server/src/gateway/connections/chat-instance-manager.ts +++ b/packages/server/src/gateway/connections/chat-instance-manager.ts @@ -27,6 +27,8 @@ import { resolveSecretValue, } from "../secrets/index.js"; import { resolveAgentOptions } from "../services/platform-helpers.js"; +import { orgContext, tryGetOrgId } from "../../lobu/stores/org-context.js"; +import { getAgentOrganizationId } from "../../lobu/stores/postgres-stores.js"; import { ConversationStateStore, type HistoryEntry, @@ -123,6 +125,10 @@ export class ChatInstanceManager { try { if (connection.status === "active") { + // Boot runs without an HTTP request, so AsyncLocalStorage has + // no orgId. startInstance() now self-binds the connection's + // agent org so PostgresSecretStore.get() resolves per-org + // refs correctly; see comment on startInstance(). await this.startInstance(connection); } } catch (error) { @@ -500,6 +506,31 @@ export class ChatInstanceManager { // --- Private --- private async startInstance(connection: PlatformConnection): Promise { + // Multi-tenant secret resolution: PostgresSecretStore.get/put route + // by AsyncLocalStorage org context (see #516). Some callers reach + // here with org context already bound (HTTP routes via agent-routes + // middleware); others don't (boot-time initialize(), the public + // /slack/events webhook, anywhere ensureConnectionRunning() is + // triggered without an HTTP request). Self-binding the connection's + // owning org keeps per-org secret refs resolvable from every entry + // point — no caller has to remember. + const callerOrgId = tryGetOrgId(); + if (!callerOrgId && connection.templateAgentId) { + const organizationId = await getAgentOrganizationId( + connection.templateAgentId + ); + if (organizationId) { + return orgContext.run({ organizationId }, () => + this.startInstanceUnscoped(connection) + ); + } + } + return this.startInstanceUnscoped(connection); + } + + private async startInstanceUnscoped( + connection: PlatformConnection + ): Promise { try { // Resolve any `secret://` refs in the connection config to plaintext // values for the Chat SDK adapter. This is idempotent — addConnection From df5d21e595baed8ab6e9cbcfc1f94103f18479e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Emre=20Kabakc=C4=B1?= Date: Mon, 4 May 2026 22:13:12 +0100 Subject: [PATCH 2/2] chore: regen lockfile post-rebase onto #508 --- bun.lock | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bun.lock b/bun.lock index 13b4439a4..4ec3e035e 100644 --- a/bun.lock +++ b/bun.lock @@ -64,7 +64,7 @@ "lobu": "bin/lobu.js", }, "dependencies": { - "@anthropic-ai/sdk": "^0.90.0", + "@anthropic-ai/sdk": "^0.92.0", "@aws-sdk/client-bedrock": "^3.1028.0", "@aws-sdk/client-secrets-manager": "^3.1028.0", "@chat-adapter/discord": "4.26.0", @@ -427,7 +427,7 @@ "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], - "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.90.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-MzZtPabJF1b0FTDl6Z6H5ljphPwACLGP13lu8MTiB8jXaW/YXlpOp+Po2cVou3MPM5+f5toyLnul9whKCy7fBg=="], + "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.92.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-l653JFC83wCglH8H83t1xpgDurCyPyslYW1maPRdCsfuNuGbLvQjQ81sWd3Go3LWRm0jNspzAhuqAYV8r9joSw=="], "@asamuzakjp/css-color": ["@asamuzakjp/css-color@5.1.11", "", { "dependencies": { "@asamuzakjp/generational-cache": "^1.0.1", "@csstools/css-calc": "^3.2.0", "@csstools/css-color-parser": "^4.1.0", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg=="], @@ -3961,6 +3961,8 @@ "@jsonforms/core/ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], + "@lobu/server/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.90.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-MzZtPabJF1b0FTDl6Z6H5ljphPwACLGP13lu8MTiB8jXaW/YXlpOp+Po2cVou3MPM5+f5toyLnul9whKCy7fBg=="], + "@lobu/server/@sentry/node": ["@sentry/node@9.47.1", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1", "@opentelemetry/core": "^1.30.1", "@opentelemetry/instrumentation": "^0.57.2", "@opentelemetry/instrumentation-amqplib": "^0.46.1", "@opentelemetry/instrumentation-connect": "0.43.1", "@opentelemetry/instrumentation-dataloader": "0.16.1", "@opentelemetry/instrumentation-express": "0.47.1", "@opentelemetry/instrumentation-fs": "0.19.1", "@opentelemetry/instrumentation-generic-pool": "0.43.1", "@opentelemetry/instrumentation-graphql": "0.47.1", "@opentelemetry/instrumentation-hapi": "0.45.2", "@opentelemetry/instrumentation-http": "0.57.2", "@opentelemetry/instrumentation-ioredis": "0.47.1", "@opentelemetry/instrumentation-kafkajs": "0.7.1", "@opentelemetry/instrumentation-knex": "0.44.1", "@opentelemetry/instrumentation-koa": "0.47.1", "@opentelemetry/instrumentation-lru-memoizer": "0.44.1", "@opentelemetry/instrumentation-mongodb": "0.52.0", "@opentelemetry/instrumentation-mongoose": "0.46.1", "@opentelemetry/instrumentation-mysql": "0.45.1", "@opentelemetry/instrumentation-mysql2": "0.45.2", "@opentelemetry/instrumentation-pg": "0.51.1", "@opentelemetry/instrumentation-redis-4": "0.46.1", "@opentelemetry/instrumentation-tedious": "0.18.1", "@opentelemetry/instrumentation-undici": "0.10.1", "@opentelemetry/resources": "^1.30.1", "@opentelemetry/sdk-trace-base": "^1.30.1", "@opentelemetry/semantic-conventions": "^1.34.0", "@prisma/instrumentation": "6.11.1", "@sentry/core": "9.47.1", "@sentry/node-core": "9.47.1", "@sentry/opentelemetry": "9.47.1", "import-in-the-middle": "^1.14.2", "minimatch": "^9.0.0" } }, "sha512-CDbkasBz3fnWRKSFs6mmaRepM2pa+tbZkrqhPWifFfIkJDidtVW40p6OnquTvPXyPAszCnDZRnZT14xyvNmKPQ=="], "@lobu/server/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],