Skip to content

Commit 60f0032

Browse files
committed
fix(envid): fix envId management bugs and add comprehensive tests 🐛
- Fix timeout configuration from 60000000ms to 600000ms (10 minutes) - Fix cloudrun.ts to use getEnvId instead of process.env.TCB_ENV_ID - Fix envId cache synchronization issue (Critical - fixes environment mismatch) - Remove file-based cache to prevent multi-process conflicts - Optimize getCloudBaseManager to use cached envId when available - Add getCachedEnvId method for optimization - Export envManager for testing - Add comprehensive test suite for envId management Fixes the critical bug where switching accounts/environments in CodeBuddy would cause tools to operate on wrong environment due to stale cache.
1 parent e9c1c3b commit 60f0032

File tree

9 files changed

+274
-103
lines changed

9 files changed

+274
-103
lines changed

mcp/src/cloudbase-manager.ts

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import CloudBase from "@cloudbase/manager-node";
22
import { getLoginState } from './auth.js';
3-
import { autoSetupEnvironmentId, loadEnvIdFromUserConfig, saveEnvIdToUserConfig } from './tools/interactive.js';
3+
import { autoSetupEnvironmentId } from './tools/interactive.js';
44
import { CloudBaseOptions } from './types.js';
55
import { debug, error } from './utils/logger.js';
6-
const ENV_ID_TIMEOUT = 60000000; // 60000 seconds
6+
const ENV_ID_TIMEOUT = 600000; // 10 minutes (600 seconds) - matches InteractiveServer timeout
77

88
// 统一的环境ID管理类
99
class EnvironmentManager {
@@ -59,15 +59,7 @@ class EnvironmentManager {
5959
return this.cachedEnvId;
6060
}
6161

62-
// 2. 从配置文件读取
63-
const fileEnvId = await loadEnvIdFromUserConfig();
64-
if (fileEnvId) {
65-
debug('从配置文件读取到环境ID:', fileEnvId);
66-
this._setCachedEnvId(fileEnvId);
67-
return fileEnvId;
68-
}
69-
70-
// 3. 自动设置环境ID
62+
// 2. 自动设置环境ID
7163
debug('未找到环境ID,尝试自动设置...');
7264
const autoEnvId = await autoSetupEnvironmentId();
7365
if (!autoEnvId) {
@@ -93,9 +85,12 @@ class EnvironmentManager {
9385
// 手动设置环境ID(用于外部调用)
9486
async setEnvId(envId: string) {
9587
this._setCachedEnvId(envId);
96-
// 同步保存到配置文件
97-
await saveEnvIdToUserConfig(envId);
98-
debug('手动设置环境ID并保存到文件:', envId);
88+
debug('手动设置环境ID并更新缓存:', envId);
89+
}
90+
91+
// Get cached envId without triggering fetch (for optimization)
92+
getCachedEnvId(): string | null {
93+
return this.cachedEnvId;
9994
}
10095
}
10196

@@ -147,10 +142,26 @@ export async function getCloudBaseManager(options: GetManagerOptions = {}): Prom
147142

148143
let finalEnvId: string | undefined;
149144
if (requireEnvId) {
150-
finalEnvId = await envManager.getEnvId();
145+
// Optimize: Check if envManager has cached envId first (fast path)
146+
// If cached, use it directly; otherwise check loginEnvId before calling getEnvId()
147+
// This avoids unnecessary async calls when we have a valid envId available
148+
const cachedEnvId = envManager.getCachedEnvId();
149+
if (cachedEnvId) {
150+
debug('使用 envManager 缓存的环境ID:', cachedEnvId);
151+
finalEnvId = cachedEnvId;
152+
} else if (loginEnvId) {
153+
// If no cache but loginState has envId, use it to avoid triggering auto-setup
154+
debug('使用 loginState 中的环境ID:', loginEnvId);
155+
finalEnvId = loginEnvId;
156+
} else {
157+
// Only call envManager.getEnvId() when neither cache nor loginState has envId
158+
// This may trigger auto-setup flow
159+
finalEnvId = await envManager.getEnvId();
160+
}
151161
}
152162

153-
// envId 优先顺序:获取到的envId > loginState中的envId > undefined
163+
// envId priority: envManager.cachedEnvId > envManager.getEnvId() > loginState.envId > undefined
164+
// Note: envManager.cachedEnvId has highest priority as it reflects user's latest environment switch
154165
const manager = new CloudBase({
155166
secretId,
156167
secretKey,

mcp/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ export {
3333
getCloudBaseManager,
3434
getEnvId,
3535
resetCloudBaseManagerCache,
36-
createCloudBaseManagerWithOptions
36+
createCloudBaseManagerWithOptions,
37+
envManager
3738
} from "./cloudbase-manager.js";
3839

3940
export type { InteractiveResult } from "./interactive-server.js";

mcp/src/tools/cloudrun.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { spawn } from 'child_process';
22
import fs from 'fs';
33
import path from 'path';
44
import { z } from "zod";
5-
import { getCloudBaseManager } from '../cloudbase-manager.js';
5+
import { getCloudBaseManager, getEnvId } from '../cloudbase-manager.js';
66
import { ExtendedMcpServer } from '../server.js';
77

88
// CloudRun service types
@@ -450,8 +450,9 @@ exports.main = function (event, context) {
450450
fs.writeFileSync(path.join(projectDir, 'index.js'), indexJsContent);
451451

452452
// Generate cloudbaserc.json
453+
const currentEnvId = await getEnvId(cloudBaseOptions);
453454
const cloudbasercContent = {
454-
envId: process.env.TCB_ENV_ID || '',
455+
envId: currentEnvId,
455456
cloudrun: {
456457
name: input.serverName
457458
}
@@ -587,9 +588,10 @@ for await (let x of res.textStream) {
587588
const result = await cloudrunService.deploy(deployParams);
588589

589590
// Generate cloudbaserc.json configuration file
591+
const currentEnvId = await getEnvId(cloudBaseOptions);
590592
const cloudbasercPath = path.join(targetPath, 'cloudbaserc.json');
591593
const cloudbasercContent = {
592-
envId: process.env.TCB_ENV_ID || '',
594+
envId: currentEnvId,
593595
cloudrun: {
594596
name: input.serverName
595597
}
@@ -769,9 +771,10 @@ for await (let x of res.textStream) {
769771
});
770772

771773
// Generate cloudbaserc.json configuration file
774+
const currentEnvId = await getEnvId(cloudBaseOptions);
772775
const cloudbasercPath = path.join(targetPath, 'cloudbaserc.json');
773776
const cloudbasercContent = {
774-
envId: process.env.TCB_ENV_ID || '',
777+
envId: currentEnvId,
775778
cloudrun: {
776779
name: input.serverName
777780
}
@@ -851,9 +854,10 @@ for await (let x of res.textStream) {
851854
});
852855

853856
// Generate cloudbaserc.json configuration file
857+
const currentEnvId = await getEnvId(cloudBaseOptions);
854858
const cloudbasercPath = path.join(targetPath, input.serverName, 'cloudbaserc.json');
855859
const cloudbasercContent = {
856-
envId: process.env.TCB_ENV_ID || '',
860+
envId: currentEnvId,
857861
cloudrun: {
858862
name: input.serverName
859863
}

mcp/src/tools/env.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { logout } from '../auth.js';
33
import { getCloudBaseManager, resetCloudBaseManagerCache } from '../cloudbase-manager.js';
44
import { ExtendedMcpServer } from '../server.js';
55
import { debug } from '../utils/logger.js';
6-
import { _promptAndSetEnvironmentId, clearUserEnvId } from './interactive.js';
6+
import { _promptAndSetEnvironmentId } from './interactive.js';
77

88
export function registerEnvTools(server: ExtendedMcpServer) {
99
// 获取 cloudBaseOptions,如果没有则为 undefined
@@ -84,8 +84,7 @@ export function registerEnvTools(server: ExtendedMcpServer) {
8484
try {
8585
// 登出账户
8686
await logout();
87-
// 清理环境ID配置
88-
await clearUserEnvId();
87+
// 清理环境ID缓存
8988
resetCloudBaseManagerCache();
9089

9190
return {

mcp/src/tools/interactive.ts

Lines changed: 12 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
import fs from 'fs/promises';
2-
import os from 'os';
3-
import path from 'path';
41
import { z } from "zod";
52
import { getLoginState } from '../auth.js';
6-
import { getCloudBaseManager } from '../cloudbase-manager.js';
3+
import { envManager, getCloudBaseManager } from '../cloudbase-manager.js';
74
import { getInteractiveServer } from "../interactive-server.js";
85
import { ExtendedMcpServer } from '../server.js';
9-
import { debug, warn } from '../utils/logger.js';
6+
import { debug } from '../utils/logger.js';
107

118

129
export function registerInteractiveTools(server: ExtendedMcpServer) {
@@ -116,7 +113,12 @@ export async function _promptAndSetEnvironmentId(autoSelectSingle: boolean, serv
116113
}
117114

118115
// 2. 获取可用环境列表
119-
const cloudbase = await getCloudBaseManager({requireEnvId: false});
116+
// Fix: Pass cloudBaseOptions to ensure correct environment context
117+
const serverCloudBaseOptions = server?.cloudBaseOptions;
118+
const cloudbase = await getCloudBaseManager({
119+
requireEnvId: false,
120+
cloudBaseOptions: serverCloudBaseOptions
121+
});
120122
let envResult;
121123
try {
122124
envResult = await cloudbase.env.listEnvs();
@@ -142,79 +144,17 @@ export async function _promptAndSetEnvironmentId(autoSelectSingle: boolean, serv
142144
selectedEnvId = result.data;
143145
}
144146

145-
// 4. 保存环境ID配置
147+
// 4. 更新环境ID缓存
146148
if (selectedEnvId) {
147-
await saveEnvIdToUserConfig(selectedEnvId);
148-
debug('环境ID已保存到配置文件:', selectedEnvId);
149+
// Update memory cache and process.env to prevent environment mismatch
150+
await envManager.setEnvId(selectedEnvId);
151+
debug('环境ID已更新缓存:', selectedEnvId);
149152
}
150153

151154
return { selectedEnvId, cancelled: false };
152155

153156
}
154157

155-
// 获取用户配置文件路径
156-
function getUserConfigPath(): string {
157-
return path.join(os.homedir(), '.cloudbase-env-id');
158-
}
159-
160-
// 保存环境ID到用户配置文件
161-
export async function saveEnvIdToUserConfig(envId: string): Promise<void> {
162-
const configPath = getUserConfigPath();
163-
164-
try {
165-
const config = {
166-
envId,
167-
updatedAt: new Date().toISOString(),
168-
version: '1.0'
169-
};
170-
171-
await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf8');
172-
debug('环境ID配置已保存到文件:', configPath);
173-
174-
} catch (error) {
175-
console.error('保存环境ID配置失败:', error);
176-
throw error;
177-
}
178-
}
179-
180-
// 从用户配置文件读取环境ID
181-
export async function loadEnvIdFromUserConfig(): Promise<string | null> {
182-
const configPath = getUserConfigPath();
183-
184-
try {
185-
const configContent = await fs.readFile(configPath, 'utf8');
186-
const config = JSON.parse(configContent);
187-
const envId = config.envId || null;
188-
if (!envId) {
189-
warn(`Config file ${configPath} found, but 'envId' property is missing or empty.`);
190-
} else {
191-
debug('从配置文件加载环境ID:', envId);
192-
}
193-
return envId;
194-
} catch (err: any) {
195-
// 文件不存在是正常情况,不应告警。只在文件存在但有问题时告警。
196-
if (err.code !== 'ENOENT') {
197-
warn(`Failed to load envId from config file at ${configPath}. Error: ${err.message}`);
198-
} else {
199-
debug(`Env config file not found at ${configPath}, which is expected if not set.`);
200-
}
201-
return null;
202-
}
203-
}
204-
205-
// 清理用户环境ID配置
206-
export async function clearUserEnvId(): Promise<void> {
207-
const configPath = getUserConfigPath();
208-
209-
try {
210-
await fs.unlink(configPath);
211-
debug('环境ID配置文件已删除:', configPath);
212-
} catch (error) {
213-
// 文件不存在或删除失败,忽略错误
214-
debug('环境ID配置文件不存在或已清理:', configPath);
215-
}
216-
}
217-
218158
// 自动设置环境ID(无需MCP工具调用)
219159
export async function autoSetupEnvironmentId(): Promise<string | null> {
220160
try {

mcp/src/utils/cloud-mode.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ export function shouldRegisterTool(toolName: string): boolean {
9292

9393
// Setup tools - local config file operations
9494
'setupEnvironmentId',
95-
'clearUserEnvId',
9695

9796
// Interactive tools - local server and file operations
9897
'interactiveDialog',

mcp/src/utils/telemetry.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import crypto from 'crypto';
33
import https from 'https';
44
import http from 'http';
55
import { debug } from './logger.js';
6-
import {loadEnvIdFromUserConfig } from '../tools/interactive.js';
76
import { CloudBaseOptions } from '../types.js';
87

98
// 构建时注入的版本号
@@ -253,10 +252,9 @@ export const reportToolCall = async (params: {
253252
// 安全获取环境ID,优先使用传入的配置
254253
let envId: string | undefined;
255254
try {
256-
// 优先级:传入配置 > 环境变量 > 配置文件 > unknown
255+
// 优先级:传入配置 > 环境变量 > unknown
257256
envId = params.cloudBaseOptions?.envId ||
258257
process.env.CLOUDBASE_ENV_ID ||
259-
await loadEnvIdFromUserConfig() ||
260258
'unknown';
261259
} catch (err) {
262260
// 忽略错误,使用 unknown
@@ -321,10 +319,9 @@ export const reportToolkitLifecycle = async (params: {
321319
// 安全获取环境ID,优先使用传入的配置
322320
let envId: string | undefined;
323321
try {
324-
// 优先级:传入配置 > 环境变量 > 配置文件 > unknown
322+
// 优先级:传入配置 > 环境变量 > unknown
325323
envId = params.cloudBaseOptions?.envId ||
326324
process.env.CLOUDBASE_ENV_ID ||
327-
await loadEnvIdFromUserConfig() ||
328325
'unknown';
329326
} catch (err) {
330327
// 忽略错误,使用 unknown

specs/mysql-database-support/design-review.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,4 @@
9696
- SQL 操作通过 `query` 参数传递 SQL 语句
9797
- NO-SQL 操作通过专门的参数(`documents`, `query`, `update` 等)
9898

99+

0 commit comments

Comments
 (0)