Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion discord-read/server/lib/trigger-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,20 @@ class LazyStudioKV implements TriggerStorage {
}

async get(connectionId: string) {
return this.inner?.get(connectionId) ?? null;
const result = (await this.inner?.get(connectionId)) ?? null;
console.log(
`[TriggerStorage] GET ${connectionId}: ${result ? "found credentials" : "empty"} (ready=${this.isReady})`,
);
return result;
}
async set(connectionId: string, state: any) {
console.log(
`[TriggerStorage] SET ${connectionId}: saving credentials (ready=${this.isReady})`,
);
await this.inner?.set(connectionId, state);
}
async delete(connectionId: string) {
console.log(`[TriggerStorage] DELETE ${connectionId}`);
await this.inner?.delete(connectionId);
}
}
Expand Down
50 changes: 37 additions & 13 deletions discord-read/server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ const runtime = withRuntime<Env, typeof StateSchema, Registry>({
}

// If we have a connection ID, sync to config-cache
console.log(
`[CONFIG] Save check: connectionId=${connectionId || "MISSING"}, organizationId=${organizationId || "MISSING"}, meshUrl=${meshUrl ? "yes" : "MISSING"}, authorization=${authorization ? "yes" : "MISSING"}`,
);
if (connectionId && organizationId && meshUrl) {
const existingConfig = await getDiscordConfig(connectionId);

Expand Down Expand Up @@ -169,6 +172,13 @@ const runtime = withRuntime<Env, typeof StateSchema, Registry>({
console.log(
`[CONFIG] Authorized Guilds: ${authorizedGuilds.length > 0 ? authorizedGuilds.join(", ") : "all"}`,
);
console.log(
`[CONFIG] Bot token: ${botToken ? `${botToken.slice(0, 10)}...${botToken.slice(-4)}` : "MISSING"}`,
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Avoid logging any portion of the Discord bot token; log only presence/absence to prevent secret leakage in observability systems.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At discord-read/server/main.ts, line 176:

<comment>Avoid logging any portion of the Discord bot token; log only presence/absence to prevent secret leakage in observability systems.</comment>

<file context>
@@ -169,6 +172,13 @@ const runtime = withRuntime<Env, typeof StateSchema, Registry>({
           `[CONFIG] Authorized Guilds: ${authorizedGuilds.length > 0 ? authorizedGuilds.join(", ") : "all"}`,
         );
+        console.log(
+          `[CONFIG] Bot token: ${botToken ? `${botToken.slice(0, 10)}...${botToken.slice(-4)}` : "MISSING"}`,
+        );
+      } else {
</file context>
Suggested change
`[CONFIG] Bot token: ${botToken ? `${botToken.slice(0, 10)}...${botToken.slice(-4)}` : "MISSING"}`,
`[CONFIG] Bot token: ${botToken ? "configured" : "MISSING"}`,
Fix with Cubic

);
} else {
console.warn(
`[CONFIG] ⚠️ Cannot save config — missing: ${!connectionId ? "connectionId " : ""}${!organizationId ? "organizationId " : ""}${!meshUrl ? "meshUrl" : ""}`,
);
}

// Auto-initialize Discord client for this connection
Expand Down Expand Up @@ -308,6 +318,11 @@ async function bootstrapFromSupabase(): Promise<void> {

console.log(`[BOOTSTRAP] Found ${rows.length} saved connection(s)`);

// Deduplicate by bot_token — only start one Discord client per unique token.
// Multiple connections may share the same bot; we pick the first and register
// the rest as aliases so config-cache / triggers still work for them.
const startedTokens = new Set<string>();

for (const row of rows) {
const connectionId = row.connection_id;

Expand All @@ -325,19 +340,7 @@ async function bootstrapFromSupabase(): Promise<void> {
triggerStorage.configure(meshUrl, meshApiKey);
}

// Build a synthetic env so ensureBotRunning can resolve the token
const syntheticEnv = {
MESH_REQUEST_CONTEXT: {
connectionId,
organizationId: row.organization_id,
meshUrl,
token: meshApiKey || undefined,
authorization: `Bearer ${row.bot_token}`,
state: {},
},
} as unknown as Env;

// Sync config to in-memory cache
// Sync config to in-memory cache (always, even for duplicate tokens)
const { setDiscordConfig } = await import("./lib/config-cache.ts");
await setDiscordConfig({
connectionId,
Expand All @@ -353,13 +356,34 @@ async function bootstrapFromSupabase(): Promise<void> {
commandPrefix: row.command_prefix || "!",
});

// Skip starting a second Discord client for the same bot token
if (startedTokens.has(row.bot_token)) {
console.log(
`[BOOTSTRAP] Skipping ${connectionId} — bot already started for this token`,
);
continue;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Skipping duplicate-token connections without registering an alias instance allows later onChange for that connection to start another Discord client with the same token.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At discord-read/server/main.ts, line 354:

<comment>Skipping duplicate-token connections without registering an alias instance allows later onChange for that connection to start another Discord client with the same token.</comment>

<file context>
@@ -353,13 +346,34 @@ async function bootstrapFromSupabase(): Promise<void> {
+        console.log(
+          `[BOOTSTRAP] Skipping ${connectionId} — bot already started for this token`,
+        );
+        continue;
+      }
+
</file context>
Fix with Cubic

}

// Build a synthetic env so ensureBotRunning can resolve the token
const syntheticEnv = {
MESH_REQUEST_CONTEXT: {
connectionId,
organizationId: row.organization_id,
meshUrl,
token: meshApiKey || undefined,
authorization: `Bearer ${row.bot_token}`,
state: {},
},
} as unknown as Env;

// Ensure instance is created (superAdmins come from StateSchema on next onChange)
getOrCreateInstance(connectionId, syntheticEnv);

try {
console.log(`[BOOTSTRAP] Starting bot for ${connectionId}...`);
const started = await ensureBotRunning(syntheticEnv);
if (started) {
startedTokens.add(row.bot_token);
console.log(`[BOOTSTRAP] Bot started for ${connectionId} ✓`);
} else {
console.log(`[BOOTSTRAP] Bot failed to start for ${connectionId}`);
Expand Down
10 changes: 7 additions & 3 deletions discord-read/server/tools/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,14 @@ export const createSaveConfigTool = (env: Env) =>

// Get connection ID from env
const connectionId =
env.MESH_REQUEST_CONTEXT?.connectionId || "default-connection";
env?.MESH_REQUEST_CONTEXT?.connectionId || "default-connection";
const organizationId =
env.MESH_REQUEST_CONTEXT?.organizationId || "default-org";
const meshUrl = env.MESH_REQUEST_CONTEXT?.meshUrl || "";
env?.MESH_REQUEST_CONTEXT?.organizationId || "default-org";
const meshUrl = env?.MESH_REQUEST_CONTEXT?.meshUrl || "";

console.log(
`[Tool] DISCORD_SAVE_CONFIG: connectionId=${connectionId}, organizationId=${organizationId}, meshUrl=${meshUrl ? "yes" : "EMPTY"}, env=${!!env}, runtimeContext=${!!runtimeContext}`,
);

// Create config object
const config: DiscordConfig = {
Expand Down
Loading